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 }