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 }