github.com/annwntech/go-micro/v2@v2.9.5/runtime/service.go (about)

     1  package runtime
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/annwntech/go-micro/v2/logger"
    12  	"github.com/annwntech/go-micro/v2/runtime/local/build"
    13  	"github.com/annwntech/go-micro/v2/runtime/local/process"
    14  	proc "github.com/annwntech/go-micro/v2/runtime/local/process/os"
    15  )
    16  
    17  type service struct {
    18  	sync.RWMutex
    19  
    20  	running bool
    21  	closed  chan bool
    22  	err     error
    23  	updated time.Time
    24  
    25  	retries    int
    26  	maxRetries int
    27  
    28  	// output for logs
    29  	output io.Writer
    30  
    31  	// service to manage
    32  	*Service
    33  	// process creator
    34  	Process *proc.Process
    35  	// Exec
    36  	Exec *process.Executable
    37  	// process pid
    38  	PID *process.PID
    39  }
    40  
    41  func newService(s *Service, c CreateOptions) *service {
    42  	var exec string
    43  	var args []string
    44  
    45  	// set command
    46  	exec = strings.Join(c.Command, " ")
    47  	args = c.Args
    48  
    49  	return &service{
    50  		Service: s,
    51  		Process: new(proc.Process),
    52  		Exec: &process.Executable{
    53  			Package: &build.Package{
    54  				Name: s.Name,
    55  				Path: exec,
    56  			},
    57  			Env:  c.Env,
    58  			Args: args,
    59  			Dir:  s.Source,
    60  		},
    61  		closed:     make(chan bool),
    62  		output:     c.Output,
    63  		updated:    time.Now(),
    64  		maxRetries: c.Retries,
    65  	}
    66  }
    67  
    68  func (s *service) streamOutput() {
    69  	go io.Copy(s.output, s.PID.Output)
    70  	go io.Copy(s.output, s.PID.Error)
    71  }
    72  
    73  func (s *service) shouldStart() bool {
    74  	if s.running {
    75  		return false
    76  	}
    77  	return s.retries <= s.maxRetries
    78  }
    79  
    80  func (s *service) key() string {
    81  	return fmt.Sprintf("%v:%v", s.Name, s.Version)
    82  }
    83  
    84  func (s *service) ShouldStart() bool {
    85  	s.RLock()
    86  	defer s.RUnlock()
    87  	return s.shouldStart()
    88  }
    89  
    90  func (s *service) Running() bool {
    91  	s.RLock()
    92  	defer s.RUnlock()
    93  	return s.running
    94  }
    95  
    96  // Start starts the service
    97  func (s *service) Start() error {
    98  	s.Lock()
    99  	defer s.Unlock()
   100  
   101  	if !s.shouldStart() {
   102  		return nil
   103  	}
   104  
   105  	// reset
   106  	s.err = nil
   107  	s.closed = make(chan bool)
   108  	s.retries = 0
   109  
   110  	if s.Metadata == nil {
   111  		s.Metadata = make(map[string]string)
   112  	}
   113  	s.Status("starting", nil)
   114  
   115  	// TODO: pull source & build binary
   116  	if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   117  		logger.Debugf("Runtime service %s forking new process", s.Service.Name)
   118  	}
   119  
   120  	p, err := s.Process.Fork(s.Exec)
   121  	if err != nil {
   122  		s.Status("error", err)
   123  		return err
   124  	}
   125  	// set the pid
   126  	s.PID = p
   127  	// set to running
   128  	s.running = true
   129  	// set status
   130  	s.Status("running", nil)
   131  	// set started
   132  	s.Metadata["started"] = time.Now().Format(time.RFC3339)
   133  
   134  	if s.output != nil {
   135  		s.streamOutput()
   136  	}
   137  
   138  	// wait and watch
   139  	go s.Wait()
   140  
   141  	return nil
   142  }
   143  
   144  // Status updates the status of the service. Assumes it's called under a lock as it mutates state
   145  func (s *service) Status(status string, err error) {
   146  	s.Metadata["lastStatusUpdate"] = time.Now().Format(time.RFC3339)
   147  	s.Metadata["status"] = status
   148  	if err == nil {
   149  		delete(s.Metadata, "error")
   150  		return
   151  	}
   152  	s.Metadata["error"] = err.Error()
   153  
   154  }
   155  
   156  // Stop stops the service
   157  func (s *service) Stop() error {
   158  	s.Lock()
   159  	defer s.Unlock()
   160  
   161  	select {
   162  	case <-s.closed:
   163  		return nil
   164  	default:
   165  		close(s.closed)
   166  		s.running = false
   167  		s.retries = 0
   168  		if s.PID == nil {
   169  			return nil
   170  		}
   171  
   172  		// set status
   173  		s.Status("stopping", nil)
   174  
   175  		// kill the process
   176  		err := s.Process.Kill(s.PID)
   177  		if err == nil {
   178  			// wait for it to exit
   179  			s.Process.Wait(s.PID)
   180  		}
   181  
   182  		// set status
   183  		s.Status("stopped", err)
   184  
   185  		// return the kill error
   186  		return err
   187  	}
   188  }
   189  
   190  // Error returns the last error service has returned
   191  func (s *service) Error() error {
   192  	s.RLock()
   193  	defer s.RUnlock()
   194  	return s.err
   195  }
   196  
   197  // Wait waits for the service to finish running
   198  func (s *service) Wait() {
   199  	// wait for process to exit
   200  	s.RLock()
   201  	thisPID := s.PID
   202  	s.RUnlock()
   203  	err := s.Process.Wait(thisPID)
   204  
   205  	s.Lock()
   206  	defer s.Unlock()
   207  
   208  	if s.PID.ID != thisPID.ID {
   209  		// trying to update when it's already been switched out, ignore
   210  		logger.Warnf("Trying to update a process status but PID doesn't match. Old %s, New %s. Skipping update.", thisPID.ID, s.PID.ID)
   211  		return
   212  	}
   213  
   214  	// save the error
   215  	if err != nil {
   216  		if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   217  			logger.Errorf("Service %s terminated with error %s", s.Name, err)
   218  		}
   219  		s.retries++
   220  		s.Status("error", err)
   221  		s.Metadata["retries"] = strconv.Itoa(s.retries)
   222  
   223  		s.err = err
   224  	} else {
   225  		s.Status("done", nil)
   226  	}
   227  
   228  	// no longer running
   229  	s.running = false
   230  }