github.com/ergo-services/ergo@v1.999.224/gen/supervisor.go (about) 1 package gen 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/ergo-services/ergo/etf" 8 "github.com/ergo-services/ergo/lib" 9 ) 10 11 // SupervisorBehavior interface 12 type SupervisorBehavior interface { 13 ProcessBehavior 14 Init(args ...etf.Term) (SupervisorSpec, error) 15 } 16 17 // SupervisorStrategy 18 type SupervisorStrategy struct { 19 Type SupervisorStrategyType 20 Intensity uint16 21 Period uint16 22 Restart SupervisorStrategyRestart 23 } 24 25 // SupervisorStrategyType 26 type SupervisorStrategyType = string 27 28 // SupervisorStrategyRestart 29 type SupervisorStrategyRestart = string 30 31 const ( 32 // Restart strategies: 33 34 // SupervisorRestartIntensity 35 SupervisorRestartIntensity = uint16(10) 36 37 // SupervisorRestartPeriod 38 SupervisorRestartPeriod = uint16(10) 39 40 // SupervisorStrategyOneForOne If one child process terminates and is to be restarted, only 41 // that child process is affected. This is the default restart strategy. 42 SupervisorStrategyOneForOne = SupervisorStrategyType("one_for_one") 43 44 // SupervisorStrategyOneForAll If one child process terminates and is to be restarted, all other 45 // child processes are terminated and then all child processes are restarted. 46 SupervisorStrategyOneForAll = SupervisorStrategyType("one_for_all") 47 48 // SupervisorStrategyRestForOne If one child process terminates and is to be restarted, 49 // the 'rest' of the child processes (that is, the child 50 // processes after the terminated child process in the start order) 51 // are terminated. Then the terminated child process and all 52 // child processes after it are restarted 53 SupervisorStrategyRestForOne = SupervisorStrategyType("rest_for_one") 54 55 // SupervisorStrategySimpleOneForOne A simplified one_for_one supervisor, where all 56 // child processes are dynamically added instances 57 // of the same process type, that is, running the same code. 58 SupervisorStrategySimpleOneForOne = SupervisorStrategyType("simple_one_for_one") 59 60 // Restart types: 61 62 // SupervisorStrategyRestartPermanent child process is always restarted 63 SupervisorStrategyRestartPermanent = SupervisorStrategyRestart("permanent") 64 65 // SupervisorStrategyRestartTemporary child process is never restarted 66 // (not even when the supervisor restart strategy is rest_for_one 67 // or one_for_all and a sibling death causes the temporary process 68 // to be terminated) 69 SupervisorStrategyRestartTemporary = SupervisorStrategyRestart("temporary") 70 71 // SupervisorStrategyRestartTransient child process is restarted only if 72 // it terminates abnormally, that is, with an exit reason other 73 // than normal, shutdown. 74 SupervisorStrategyRestartTransient = SupervisorStrategyRestart("transient") 75 76 supervisorChildStateStart = 0 77 supervisorChildStateRunning = 1 78 supervisorChildStateDisabled = -1 79 ) 80 81 type supervisorChildState int 82 83 // SupervisorSpec 84 type SupervisorSpec struct { 85 Name string 86 Children []SupervisorChildSpec 87 Strategy SupervisorStrategy 88 restarts []int64 89 } 90 91 // SupervisorChildSpec 92 type SupervisorChildSpec struct { 93 Name string 94 Child ProcessBehavior 95 Options ProcessOptions 96 Args []etf.Term 97 98 state supervisorChildState // for internal usage 99 process Process 100 } 101 102 // Supervisor is implementation of ProcessBehavior interface 103 type Supervisor struct{} 104 105 type messageStartChild struct { 106 name string 107 args []etf.Term 108 } 109 110 // ProcessInit 111 func (sv *Supervisor) ProcessInit(p Process, args ...etf.Term) (ProcessState, error) { 112 behavior, ok := p.Behavior().(SupervisorBehavior) 113 if !ok { 114 return ProcessState{}, fmt.Errorf("ProcessInit: not a SupervisorBehavior") 115 } 116 spec, err := behavior.Init(args...) 117 if err != nil { 118 return ProcessState{}, err 119 } 120 lib.Log("[%s] SUPERVISOR %q with restart strategy: %s[%s] ", p.NodeName(), p.Name(), spec.Strategy.Type, spec.Strategy.Restart) 121 122 p.SetTrapExit(true) 123 return ProcessState{ 124 Process: p, 125 State: &spec, 126 }, nil 127 } 128 129 // ProcessLoop 130 func (sv *Supervisor) ProcessLoop(ps ProcessState, started chan<- bool) string { 131 spec := ps.State.(*SupervisorSpec) 132 if spec.Strategy.Type != SupervisorStrategySimpleOneForOne { 133 startChildren(ps, spec) 134 } 135 136 waitTerminatingProcesses := []etf.Pid{} 137 chs := ps.ProcessChannels() 138 139 started <- true 140 for { 141 select { 142 case ex := <-chs.GracefulExit: 143 if ex.From == ps.Self() { 144 // stop supervisor gracefully 145 for i := range spec.Children { 146 p := spec.Children[i].process 147 if p != nil && p.IsAlive() { 148 p.Exit(ex.Reason) 149 } 150 } 151 return ex.Reason 152 } 153 waitTerminatingProcesses = handleMessageExit(ps, ex, spec, waitTerminatingProcesses) 154 155 case <-ps.Context().Done(): 156 return "kill" 157 158 case direct := <-chs.Direct: 159 value, err := handleDirect(ps, spec, direct.Message) 160 ps.PutSyncReply(direct.Ref, value, err) 161 162 case <-chs.Mailbox: 163 // do nothing 164 } 165 } 166 } 167 168 // StartChild dynamically starts a child process with given name of child spec which is defined by Init call. 169 func (sv *Supervisor) StartChild(supervisor Process, name string, args ...etf.Term) (Process, error) { 170 message := messageStartChild{ 171 name: name, 172 args: args, 173 } 174 value, err := supervisor.Direct(message) 175 if err != nil { 176 return nil, err 177 } 178 process, ok := value.(Process) 179 if !ok { 180 return nil, fmt.Errorf("internal error: can't start child %#v", value) 181 } 182 return process, nil 183 } 184 185 func startChildren(supervisor Process, spec *SupervisorSpec) { 186 spec.restarts = append(spec.restarts, time.Now().Unix()) 187 if len(spec.restarts) > int(spec.Strategy.Intensity) { 188 period := time.Now().Unix() - spec.restarts[0] 189 if period <= int64(spec.Strategy.Period) { 190 lib.Warning("Supervisor %q. Restart intensity is exceeded (%d restarts for %d seconds)", 191 spec.Name, spec.Strategy.Intensity, spec.Strategy.Period) 192 supervisor.Kill() 193 return 194 } 195 spec.restarts = spec.restarts[1:] 196 } 197 198 for i := range spec.Children { 199 switch spec.Children[i].state { 200 case supervisorChildStateDisabled: 201 spec.Children[i].process = nil 202 case supervisorChildStateRunning: 203 continue 204 case supervisorChildStateStart: 205 spec.Children[i].state = supervisorChildStateRunning 206 process := startChild(supervisor, spec.Children[i].Name, spec.Children[i].Child, spec.Children[i].Options, spec.Children[i].Args...) 207 spec.Children[i].process = process 208 default: 209 panic("Incorrect supervisorChildState") 210 } 211 } 212 } 213 214 func startChild(supervisor Process, name string, child ProcessBehavior, opts ProcessOptions, args ...etf.Term) Process { 215 216 opts.GroupLeader = supervisor 217 if leader := supervisor.GroupLeader(); leader != nil { 218 opts.GroupLeader = leader 219 } 220 221 // Child process shouldn't ignore supervisor termination (via TrapExit). 222 // Using the supervisor's Context makes the child terminate if the supervisor is terminated. 223 opts.Context = supervisor.Context() 224 225 process, err := supervisor.Spawn(name, opts, child, args...) 226 227 if err != nil { 228 panic(err.Error()) 229 } 230 231 supervisor.Link(process.Self()) 232 233 return process 234 } 235 236 func handleDirect(supervisor Process, spec *SupervisorSpec, message interface{}) (interface{}, error) { 237 switch m := message.(type) { 238 case MessageDirectChildren: 239 children := []etf.Pid{} 240 for i := range spec.Children { 241 if spec.Children[i].process == nil { 242 continue 243 } 244 children = append(children, spec.Children[i].process.Self()) 245 } 246 247 return children, nil 248 case messageStartChild: 249 childSpec, err := lookupSpecByName(m.name, spec.Children) 250 if err != nil { 251 return nil, err 252 } 253 childSpec.state = supervisorChildStateStart 254 if len(m.args) > 0 { 255 childSpec.Args = m.args 256 } 257 // Dinamically started child can't be registered with a name. 258 childSpec.Name = "" 259 process := startChild(supervisor, childSpec.Name, childSpec.Child, childSpec.Options, childSpec.Args...) 260 childSpec.process = process 261 spec.Children = append(spec.Children, childSpec) 262 return process, nil 263 264 default: 265 } 266 267 return nil, lib.ErrUnsupportedRequest 268 } 269 270 func handleMessageExit(p Process, exit ProcessGracefulExitRequest, spec *SupervisorSpec, wait []etf.Pid) []etf.Pid { 271 272 terminated := exit.From 273 reason := exit.Reason 274 275 isChild := false 276 // We should make sure if it was an exit message from the supervisor's child 277 for i := range spec.Children { 278 child := spec.Children[i].process 279 if child == nil { 280 continue 281 } 282 if child.Self() == terminated { 283 isChild = true 284 break 285 } 286 } 287 288 if !isChild && reason != "restart" { 289 return wait 290 } 291 292 if len(wait) > 0 { 293 for i := range wait { 294 if wait[i] == terminated { 295 wait[i] = wait[0] 296 wait = wait[1:] 297 break 298 } 299 } 300 301 if len(wait) == 0 { 302 // it was the last one. lets restart all terminated children 303 // which hasn't supervisorChildStateDisabled state 304 startChildren(p, spec) 305 } 306 307 return wait 308 } 309 310 switch spec.Strategy.Type { 311 312 case SupervisorStrategyOneForAll: 313 for i := range spec.Children { 314 if spec.Children[i].state != supervisorChildStateRunning { 315 continue 316 } 317 318 child := spec.Children[i].process 319 if child == nil { 320 continue 321 } 322 323 spec.Children[i].process = nil 324 if haveToDisableChild(spec.Strategy.Restart, reason) { 325 spec.Children[i].state = supervisorChildStateDisabled 326 break 327 } 328 329 if spec.Children[i].state == supervisorChildStateDisabled { 330 continue 331 } 332 spec.Children[i].state = supervisorChildStateStart 333 if child.Self() == terminated { 334 if len(spec.Children) == i+1 && len(wait) == 0 { 335 // it was the last one. nothing to waiting for 336 startChildren(p, spec) 337 } 338 continue 339 } 340 341 child.Exit("restart") 342 343 wait = append(wait, child.Self()) 344 } 345 346 case SupervisorStrategyRestForOne: 347 isRest := false 348 for i := range spec.Children { 349 child := spec.Children[i].process 350 if child == nil { 351 continue 352 } 353 if child.Self() == terminated { 354 isRest = true 355 spec.Children[i].process = nil 356 if haveToDisableChild(spec.Strategy.Restart, reason) { 357 spec.Children[i].state = supervisorChildStateDisabled 358 break 359 } else { 360 spec.Children[i].state = supervisorChildStateStart 361 } 362 363 if len(spec.Children) == i+1 && len(wait) == 0 { 364 // it was the last one. nothing to waiting for 365 startChildren(p, spec) 366 } 367 368 continue 369 } 370 371 if isRest && spec.Children[i].state == supervisorChildStateRunning { 372 child.Exit("restart") 373 spec.Children[i].process = nil 374 wait = append(wait, child.Self()) 375 if haveToDisableChild(spec.Strategy.Restart, "restart") { 376 spec.Children[i].state = supervisorChildStateDisabled 377 } else { 378 spec.Children[i].state = supervisorChildStateStart 379 } 380 } 381 } 382 383 case SupervisorStrategyOneForOne: 384 for i := range spec.Children { 385 child := spec.Children[i].process 386 if child == nil { 387 continue 388 } 389 if child.Self() == terminated { 390 spec.Children[i].process = nil 391 if haveToDisableChild(spec.Strategy.Restart, reason) { 392 spec.Children[i].state = supervisorChildStateDisabled 393 } else { 394 spec.Children[i].state = supervisorChildStateStart 395 } 396 397 startChildren(p, spec) 398 break 399 } 400 } 401 402 case SupervisorStrategySimpleOneForOne: 403 for i := range spec.Children { 404 child := spec.Children[i].process 405 if child == nil { 406 continue 407 } 408 if child.Self() == terminated { 409 410 if haveToDisableChild(spec.Strategy.Restart, reason) { 411 // wont be restarted due to restart strategy 412 spec.Children[i] = spec.Children[0] 413 spec.Children = spec.Children[1:] 414 break 415 } 416 417 process := startChild(p, spec.Children[i].Name, spec.Children[i].Child, spec.Children[i].Options, spec.Children[i].Args...) 418 spec.Children[i].process = process 419 break 420 } 421 } 422 } 423 // check if all children are disabled. stop this process with reason "normal" 424 shouldStop := true 425 for i := range spec.Children { 426 if spec.Children[i].state == supervisorChildStateDisabled { 427 continue 428 } 429 shouldStop = false 430 break 431 } 432 if shouldStop { 433 p.Exit("normal") 434 } 435 return wait 436 } 437 438 func haveToDisableChild(strategy SupervisorStrategyRestart, reason string) bool { 439 switch strategy { 440 case SupervisorStrategyRestartTransient: 441 if reason == "shutdown" || reason == "normal" { 442 return true 443 } 444 445 case SupervisorStrategyRestartTemporary: 446 return true 447 } 448 449 return false 450 } 451 452 func lookupSpecByName(specName string, spec []SupervisorChildSpec) (SupervisorChildSpec, error) { 453 for i := range spec { 454 if spec[i].Name == specName { 455 return spec[i], nil 456 } 457 } 458 return SupervisorChildSpec{}, fmt.Errorf("unknown child") 459 }