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  }