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 }