github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/local/service.go (about)

     1  // Licensed under the Apache License, Version 2.0 (the "License");
     2  // you may not use this file except in compliance with the License.
     3  // You may obtain a copy of the License at
     4  //
     5  //     https://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS,
     9  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  // See the License for the specific language governing permissions and
    11  // limitations under the License.
    12  //
    13  // Original source: github.com/micro/go-micro/v3/runtime/local/service.go
    14  
    15  package local
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/tickoalcantara12/micro/v3/service/logger"
    26  	"github.com/tickoalcantara12/micro/v3/service/runtime"
    27  	"github.com/tickoalcantara12/micro/v3/service/runtime/local/process"
    28  	proc "github.com/tickoalcantara12/micro/v3/service/runtime/local/process/os"
    29  )
    30  
    31  type service struct {
    32  	sync.RWMutex
    33  
    34  	running bool
    35  	closed  chan bool
    36  	err     error
    37  	updated time.Time
    38  
    39  	retries    int
    40  	maxRetries int
    41  
    42  	// output for logs
    43  	output io.Writer
    44  
    45  	stx sync.RWMutex
    46  	// service to manage
    47  	*runtime.Service
    48  	// process creator
    49  	Process *proc.Process
    50  	// Exec
    51  	Exec *process.Binary
    52  	// process pid
    53  	PID *process.PID
    54  }
    55  
    56  func newService(s *runtime.Service, c runtime.CreateOptions) *service {
    57  	var exec string
    58  	var args []string
    59  
    60  	// set command
    61  	exec = strings.Join(c.Command, " ")
    62  	args = c.Args
    63  
    64  	dir := s.Source
    65  
    66  	return &service{
    67  		Service: s,
    68  		Process: new(proc.Process),
    69  		Exec: &process.Binary{
    70  			Package: &process.Package{
    71  				Name: s.Name,
    72  				Path: exec,
    73  			},
    74  			Env:  c.Env,
    75  			Args: args,
    76  			Dir:  dir,
    77  		},
    78  		closed:     make(chan bool),
    79  		output:     c.Output,
    80  		updated:    time.Now(),
    81  		maxRetries: c.Retries,
    82  	}
    83  }
    84  
    85  func (s *service) streamOutput() {
    86  	go io.Copy(s.output, s.PID.Output)
    87  	go io.Copy(s.output, s.PID.Error)
    88  }
    89  
    90  func (s *service) shouldStart() bool {
    91  	if s.running {
    92  		return false
    93  	}
    94  	return s.retries <= s.maxRetries
    95  }
    96  
    97  func (s *service) key() string {
    98  	return fmt.Sprintf("%v:%v", s.Name, s.Version)
    99  }
   100  
   101  func (s *service) ShouldStart() bool {
   102  	s.RLock()
   103  	defer s.RUnlock()
   104  	return s.shouldStart()
   105  }
   106  
   107  func (s *service) Running() bool {
   108  	s.RLock()
   109  	defer s.RUnlock()
   110  	return s.running
   111  }
   112  
   113  // Start starts the service
   114  func (s *service) Start() error {
   115  	s.Lock()
   116  	defer s.Unlock()
   117  
   118  	if !s.shouldStart() {
   119  		return nil
   120  	}
   121  
   122  	// reset
   123  	s.err = nil
   124  	s.closed = make(chan bool)
   125  	s.retries = 0
   126  
   127  	if s.Metadata == nil {
   128  		s.Metadata = make(map[string]string)
   129  	}
   130  	s.SetStatus(runtime.Starting, nil)
   131  
   132  	// TODO: pull source & build binary
   133  	if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   134  		logger.Debugf("Runtime service %s forking new process", s.Service.Name)
   135  	}
   136  
   137  	p, err := s.Process.Fork(s.Exec)
   138  	if err != nil {
   139  		s.SetStatus(runtime.Error, err)
   140  		return err
   141  	}
   142  	// set the pid
   143  	s.PID = p
   144  	// set to running
   145  	s.running = true
   146  	// set status
   147  	s.SetStatus(runtime.Running, nil)
   148  	// set started
   149  	s.Metadata["started"] = time.Now().Format(time.RFC3339)
   150  
   151  	if s.output != nil {
   152  		s.streamOutput()
   153  	}
   154  
   155  	// wait and watch
   156  	go func() {
   157  		s.Wait()
   158  
   159  		// don't do anything if it was stopped
   160  		status := s.GetStatus()
   161  
   162  		logger.Infof("Service %s has stopped with status: %v", s.Service.Name, status)
   163  
   164  		if (status == runtime.Stopped) || (status == runtime.Stopping) {
   165  			return
   166  		}
   167  
   168  		// should we restart?
   169  		if !s.shouldStart() {
   170  			return
   171  		}
   172  
   173  		logger.Infof("Restarting service %s", s.Service.Name)
   174  
   175  		// restart the process
   176  		s.Start()
   177  	}()
   178  
   179  	return nil
   180  }
   181  
   182  func (s *service) GetStatus() runtime.ServiceStatus {
   183  	s.stx.RLock()
   184  	status := s.Service.Status
   185  	s.stx.RUnlock()
   186  	return status
   187  }
   188  
   189  // Status updates the status of the service. Assumes it's called under a lock as it mutates state
   190  func (s *service) SetStatus(status runtime.ServiceStatus, err error) {
   191  	s.stx.Lock()
   192  	defer s.stx.Unlock()
   193  
   194  	s.Service.Status = status
   195  	s.Metadata["lastStatusUpdate"] = time.Now().Format(time.RFC3339)
   196  	if err == nil {
   197  		delete(s.Metadata, "error")
   198  		return
   199  	}
   200  	s.Metadata["error"] = err.Error()
   201  
   202  }
   203  
   204  // Stop stops the service
   205  func (s *service) Stop() error {
   206  	s.Lock()
   207  	defer s.Unlock()
   208  
   209  	select {
   210  	case <-s.closed:
   211  		return nil
   212  	default:
   213  		close(s.closed)
   214  		s.running = false
   215  		s.retries = 0
   216  		if s.PID == nil {
   217  			return nil
   218  		}
   219  
   220  		// set status
   221  		s.SetStatus(runtime.Stopping, nil)
   222  
   223  		// kill the process
   224  		err := s.Process.Kill(s.PID)
   225  
   226  		if err == nil {
   227  			// wait for it to exit
   228  			s.Process.Wait(s.PID)
   229  		}
   230  
   231  		// set status
   232  		s.SetStatus(runtime.Stopped, err)
   233  
   234  		// return the kill error
   235  		return err
   236  	}
   237  }
   238  
   239  // Error returns the last error service has returned
   240  func (s *service) Error() error {
   241  	s.RLock()
   242  	defer s.RUnlock()
   243  	return s.err
   244  }
   245  
   246  // Wait waits for the service to finish running
   247  func (s *service) Wait() {
   248  	// wait for process to exit
   249  	s.RLock()
   250  	thisPID := s.PID
   251  	s.RUnlock()
   252  	err := s.Process.Wait(thisPID)
   253  
   254  	s.Lock()
   255  	defer s.Unlock()
   256  
   257  	if s.PID.ID != thisPID.ID {
   258  		// trying to update when it's already been switched out, ignore
   259  		logger.Debugf("Trying to update a process status but PID doesn't match. Old %s, New %s. Skipping update.", thisPID.ID, s.PID.ID)
   260  		return
   261  	}
   262  
   263  	// get service status
   264  	status := s.GetStatus()
   265  
   266  	// set to not running
   267  	s.running = false
   268  
   269  	if (status == runtime.Stopped) || (status == runtime.Stopping) {
   270  		return
   271  	}
   272  
   273  	// save the error
   274  	if err != nil {
   275  		if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   276  			logger.Errorf("Service %s terminated with error %s", s.Name, err)
   277  		}
   278  		s.retries++
   279  
   280  		// don't set the error
   281  		s.Metadata["retries"] = strconv.Itoa(s.retries)
   282  		s.err = err
   283  	}
   284  
   285  	// terminated without being stopped
   286  	s.SetStatus(runtime.Error, fmt.Errorf("Service %s terminated", s.Name))
   287  }