github.com/ergo-services/ergo@v1.999.224/gen/application.go (about)

     1  package gen
     2  
     3  // http://erlang.org/doc/apps/kernel/application.html
     4  
     5  import (
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/ergo-services/ergo/etf"
    11  	"github.com/ergo-services/ergo/lib"
    12  )
    13  
    14  type ApplicationStartType = string
    15  
    16  const (
    17  	// start types:
    18  
    19  	// ApplicationStartPermanent If a permanent application terminates,
    20  	// all other applications and the runtime system (node) are also terminated.
    21  	ApplicationStartPermanent ApplicationStartType = "permanent"
    22  
    23  	// ApplicationStartTemporary If a temporary application terminates,
    24  	// this is reported but no other applications are terminated.
    25  	ApplicationStartTemporary ApplicationStartType = "temporary"
    26  
    27  	// ApplicationStartTransient If a transient application terminates
    28  	// with reason normal, this is reported but no other applications are
    29  	// terminated. If a transient application terminates abnormally, that
    30  	// is with any other reason than normal, all other applications and
    31  	// the runtime system (node) are also terminated.
    32  	ApplicationStartTransient ApplicationStartType = "transient"
    33  
    34  	// EnvKeyAppSpec
    35  	EnvKeyAppSpec EnvKey = "ergo:AppSpec"
    36  )
    37  
    38  // ApplicationBehavior interface
    39  type ApplicationBehavior interface {
    40  	ProcessBehavior
    41  	Load(args ...etf.Term) (ApplicationSpec, error)
    42  	Start(process Process, args ...etf.Term)
    43  }
    44  
    45  // ApplicationSpec
    46  type ApplicationSpec struct {
    47  	sync.Mutex
    48  	Name         string
    49  	Description  string
    50  	Version      string
    51  	Lifespan     time.Duration
    52  	Applications []string
    53  	Env          map[EnvKey]interface{}
    54  	Children     []ApplicationChildSpec
    55  	Process      Process
    56  	StartType    ApplicationStartType
    57  }
    58  
    59  // ApplicationChildSpec
    60  type ApplicationChildSpec struct {
    61  	Child   ProcessBehavior
    62  	Options ProcessOptions
    63  	Name    string
    64  	Args    []etf.Term
    65  	process Process
    66  }
    67  
    68  // Application is implementation of ProcessBehavior interface
    69  type Application struct{}
    70  
    71  // ApplicationInfo
    72  type ApplicationInfo struct {
    73  	Name        string
    74  	Description string
    75  	Version     string
    76  	PID         etf.Pid
    77  }
    78  
    79  // ProcessInit
    80  func (a *Application) ProcessInit(p Process, args ...etf.Term) (ProcessState, error) {
    81  	spec := p.Env(EnvKeyAppSpec).(*ApplicationSpec)
    82  	spec, ok := p.Env(EnvKeyAppSpec).(*ApplicationSpec)
    83  	if !ok {
    84  		return ProcessState{}, fmt.Errorf("ProcessInit: not an ApplicationBehavior")
    85  	}
    86  	// remove variable from the env
    87  	p.SetEnv(EnvKeyAppSpec, nil)
    88  
    89  	p.SetTrapExit(true)
    90  
    91  	if spec.Env != nil {
    92  		for k, v := range spec.Env {
    93  			p.SetEnv(k, v)
    94  		}
    95  	}
    96  
    97  	if !a.startChildren(p, spec.Children[:]) {
    98  		a.stopChildren(p.Self(), spec.Children[:], "failed")
    99  		return ProcessState{}, fmt.Errorf("failed")
   100  	}
   101  
   102  	behavior, ok := p.Behavior().(ApplicationBehavior)
   103  	if !ok {
   104  		return ProcessState{}, fmt.Errorf("ProcessInit: not an ApplicationBehavior")
   105  	}
   106  	behavior.Start(p, args...)
   107  	spec.Process = p
   108  
   109  	return ProcessState{
   110  		Process: p,
   111  		State:   spec,
   112  	}, nil
   113  }
   114  
   115  // ProcessLoop
   116  func (a *Application) ProcessLoop(ps ProcessState, started chan<- bool) string {
   117  	spec := ps.State.(*ApplicationSpec)
   118  	defer func() { spec.Process = nil }()
   119  
   120  	if spec.Lifespan == 0 {
   121  		spec.Lifespan = time.Hour * 24 * 365 * 100 // let's define default lifespan 100 years :)
   122  	}
   123  
   124  	chs := ps.ProcessChannels()
   125  
   126  	timer := time.NewTimer(spec.Lifespan)
   127  	// timer must be stopped explicitly to prevent of timer leaks
   128  	// due to its not GCed until the timer fires
   129  	defer timer.Stop()
   130  
   131  	started <- true
   132  	for {
   133  		select {
   134  		case ex := <-chs.GracefulExit:
   135  			terminated := ex.From
   136  			reason := ex.Reason
   137  			if ex.From == ps.Self() {
   138  				childrenStopped := a.stopChildren(terminated, spec.Children, reason)
   139  				if !childrenStopped {
   140  					lib.Warning("application %q can't be stopped. Some of the children are still running", spec.Name)
   141  					continue
   142  				}
   143  				return ex.Reason
   144  			}
   145  
   146  			unknownChild := true
   147  
   148  			for i := range spec.Children {
   149  				child := spec.Children[i].process
   150  				if child == nil {
   151  					continue
   152  				}
   153  				if child.Self() == terminated {
   154  					unknownChild = false
   155  					break
   156  				}
   157  			}
   158  
   159  			if unknownChild {
   160  				continue
   161  			}
   162  
   163  			switch spec.StartType {
   164  			case ApplicationStartPermanent:
   165  				a.stopChildren(terminated, spec.Children, string(reason))
   166  				lib.Warning("Application child %s (at %s) stopped with reason %s (permanent: node is shutting down)",
   167  					terminated, ps.NodeName(), reason)
   168  				ps.NodeStop()
   169  				return "shutdown"
   170  
   171  			case ApplicationStartTransient:
   172  				if reason == "normal" || reason == "shutdown" {
   173  					lib.Warning("Application child %s (at %s) stopped with reason %s (transient)",
   174  						terminated, ps.NodeName(), reason)
   175  					continue
   176  				}
   177  				a.stopChildren(terminated, spec.Children, reason)
   178  				lib.Warning("Application child %s (at %s) stopped with reason %s. (transient: node is shutting down)",
   179  					terminated, ps.NodeName(), reason)
   180  				ps.NodeStop()
   181  				return string(reason)
   182  
   183  			case ApplicationStartTemporary:
   184  				fmt.Printf("Application child %s (at %s) stopped with reason %s (temporary)\n",
   185  					terminated, ps.NodeName(), reason)
   186  			}
   187  
   188  		case direct := <-chs.Direct:
   189  			switch direct.Message.(type) {
   190  			case MessageDirectChildren:
   191  				pids := []etf.Pid{}
   192  				for i := range spec.Children {
   193  					if spec.Children[i].process == nil {
   194  						continue
   195  					}
   196  					pids = append(pids, spec.Children[i].process.Self())
   197  				}
   198  
   199  				ps.PutSyncReply(direct.Ref, pids, nil)
   200  
   201  			default:
   202  				ps.PutSyncReply(direct.Ref, nil, lib.ErrUnsupportedRequest)
   203  			}
   204  
   205  		case <-ps.Context().Done():
   206  			// node is down or killed using p.Kill()
   207  			return "kill"
   208  
   209  		case <-timer.C:
   210  			// time to die
   211  			ps.SetTrapExit(false)
   212  			go ps.Exit("normal")
   213  
   214  		case <-chs.Mailbox:
   215  			// do nothing
   216  		}
   217  
   218  	}
   219  }
   220  
   221  func (a *Application) stopChildren(from etf.Pid, children []ApplicationChildSpec, reason string) bool {
   222  	childrenStopped := true
   223  	for i := range children {
   224  		child := children[i].process
   225  		if child == nil {
   226  			continue
   227  		}
   228  
   229  		if child.Self() == from {
   230  			continue
   231  		}
   232  
   233  		if !child.IsAlive() {
   234  			continue
   235  		}
   236  
   237  		if err := child.Exit(reason); err != nil {
   238  			childrenStopped = false
   239  			continue
   240  		}
   241  
   242  		if err := child.WaitWithTimeout(5 * time.Second); err != nil {
   243  			childrenStopped = false
   244  			continue
   245  		}
   246  
   247  		children[i].process = nil
   248  	}
   249  
   250  	return childrenStopped
   251  }
   252  
   253  func (a *Application) startChildren(parent Process, children []ApplicationChildSpec) bool {
   254  	for i := range children {
   255  		// i know, it looks weird to use the funcion from supervisor file.
   256  		// will move it to somewhere else, but let it be there for a while.
   257  		p := startChild(parent, children[i].Name, children[i].Child, children[i].Options, children[i].Args...)
   258  		if p == nil {
   259  			return false
   260  		}
   261  		children[i].process = p
   262  	}
   263  	return true
   264  }