github.com/ergo-services/ergo@v1.999.224/gen/server.go (about) 1 package gen 2 3 import ( 4 "fmt" 5 "runtime" 6 "time" 7 8 "github.com/ergo-services/ergo/etf" 9 "github.com/ergo-services/ergo/lib" 10 ) 11 12 const ( 13 DefaultCallTimeout = 5 14 ) 15 16 // ServerBehavior interface 17 type ServerBehavior interface { 18 ProcessBehavior 19 20 // methods below are optional 21 22 // Init invoked on a start Server 23 Init(process *ServerProcess, args ...etf.Term) error 24 25 // HandleCast invoked if Server received message sent with ServerProcess.Cast. 26 // Return ServerStatusStop to stop server with "normal" reason. Use ServerStatus(error) 27 // for the custom reason 28 HandleCast(process *ServerProcess, message etf.Term) ServerStatus 29 30 // HandleCall invoked if Server got sync request using ServerProcess.Call 31 HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) 32 33 // HandleDirect invoked on a direct request made with Process.Direct 34 HandleDirect(process *ServerProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) 35 36 // HandleInfo invoked if Server received message sent with Process.Send. 37 HandleInfo(process *ServerProcess, message etf.Term) ServerStatus 38 39 // Terminate invoked on a termination process. ServerProcess.State is not locked during 40 // this callback. 41 Terminate(process *ServerProcess, reason string) 42 } 43 44 // ServerStatus 45 type ServerStatus error 46 type DirectStatus error 47 48 var ( 49 ServerStatusOK ServerStatus = nil 50 ServerStatusStop ServerStatus = fmt.Errorf("stop") 51 ServerStatusIgnore ServerStatus = fmt.Errorf("ignore") 52 53 DirectStatusOK DirectStatus = nil 54 DirectStatusIgnore DirectStatus = fmt.Errorf("ignore") 55 ) 56 57 // ServerStatusStopWithReason 58 func ServerStatusStopWithReason(s string) ServerStatus { 59 return ServerStatus(fmt.Errorf(s)) 60 } 61 62 // Server is implementation of ProcessBehavior interface for Server objects 63 type Server struct { 64 ServerBehavior 65 } 66 67 // ServerFrom 68 type ServerFrom struct { 69 Pid etf.Pid 70 Ref etf.Ref 71 ReplyByAlias bool 72 } 73 74 // ServerState state of the Server process. 75 type ServerProcess struct { 76 ProcessState 77 78 behavior ServerBehavior 79 counter uint64 // total number of processed messages from mailBox 80 currentFunction string 81 82 mailbox <-chan ProcessMailboxMessage 83 original <-chan ProcessMailboxMessage 84 deferred chan ProcessMailboxMessage 85 86 waitReply *etf.Ref 87 callbackWaitReply chan *etf.Ref 88 stop chan string 89 } 90 91 type handleCallMessage struct { 92 from ServerFrom 93 message etf.Term 94 } 95 96 type handleCastMessage struct { 97 message etf.Term 98 } 99 100 type handleInfoMessage struct { 101 message etf.Term 102 } 103 104 // CastAfter a simple wrapper for Process.SendAfter to send a message in fashion of 'gen_server:cast' 105 func (sp *ServerProcess) CastAfter(to interface{}, message etf.Term, after time.Duration) CancelFunc { 106 msg := etf.Term(etf.Tuple{etf.Atom("$gen_cast"), message}) 107 return sp.SendAfter(to, msg, after) 108 } 109 110 // Cast sends a message in fashion of 'gen_server:cast'. 'to' can be a Pid, registered local name 111 // or gen.ProcessID{RegisteredName, NodeName} 112 func (sp *ServerProcess) Cast(to interface{}, message etf.Term) error { 113 msg := etf.Term(etf.Tuple{etf.Atom("$gen_cast"), message}) 114 return sp.Send(to, msg) 115 } 116 117 // Call makes outgoing sync request in fashion of 'gen_server:call'. 118 // 'to' can be Pid, registered local name or gen.ProcessID{RegisteredName, NodeName}. 119 func (sp *ServerProcess) Call(to interface{}, message etf.Term) (etf.Term, error) { 120 return sp.CallWithTimeout(to, message, DefaultCallTimeout) 121 } 122 123 // CallWithTimeout makes outgoing sync request in fashiod of 'gen_server:call' with given timeout. 124 func (sp *ServerProcess) CallWithTimeout(to interface{}, message etf.Term, timeout int) (etf.Term, error) { 125 ref := sp.MakeRef() 126 from := etf.Tuple{sp.Self(), ref} 127 msg := etf.Term(etf.Tuple{etf.Atom("$gen_call"), from, message}) 128 129 sp.PutSyncRequest(ref) 130 if err := sp.Send(to, msg); err != nil { 131 sp.CancelSyncRequest(ref) 132 return nil, err 133 } 134 sp.callbackWaitReply <- &ref 135 value, err := sp.WaitSyncReply(ref, timeout) 136 return value, err 137 138 } 139 140 // SendReply sends a reply message to the sender made ServerProcess.Call request. 141 // Useful for the case with dispatcher and pool of workers: Dispatcher process 142 // forwards Call requests (asynchronously) within a HandleCall callback to the worker(s) 143 // using ServerProcess.Cast or ServerProcess.Send but returns ServerStatusIgnore 144 // instead of ServerStatusOK; Worker process sends result using ServerProcess.SendReply 145 // method with 'from' value received from the Dispatcher. 146 func (sp *ServerProcess) SendReply(from ServerFrom, reply etf.Term) error { 147 var fromTag etf.Term 148 var to etf.Term 149 if from.ReplyByAlias { 150 // Erlang gen_server:call uses improper list for the reply ['alias'|Ref] 151 fromTag = etf.ListImproper{etf.Atom("alias"), from.Ref} 152 to = etf.Alias(from.Ref) 153 } else { 154 fromTag = from.Ref 155 to = from.Pid 156 } 157 158 if reply != nil { 159 rep := etf.Tuple{fromTag, reply} 160 return sp.Send(to, rep) 161 } 162 rep := etf.Tuple{fromTag, etf.Atom("nil")} 163 return sp.Send(to, rep) 164 } 165 166 // Reply the handling process.Direct(...) calls can be done asynchronously 167 // using gen.DirectStatusIgnore as a returning status in the HandleDirect callback. 168 // In this case, you must reply manualy using gen.ServerProcess.Reply method in any other 169 // callback. If a caller has canceled this request due to timeout it returns lib.ErrReferenceUnknown 170 func (sp *ServerProcess) Reply(ref etf.Ref, reply etf.Term, err error) error { 171 return sp.PutSyncReply(ref, reply, err) 172 } 173 174 // MessageCounter returns the total number of messages handled by Server callbacks: HandleCall, 175 // HandleCast, HandleInfo, HandleDirect 176 func (sp *ServerProcess) MessageCounter() uint64 { 177 return sp.counter 178 } 179 180 // ProcessInit 181 func (gs *Server) ProcessInit(p Process, args ...etf.Term) (ProcessState, error) { 182 behavior, ok := p.Behavior().(ServerBehavior) 183 if !ok { 184 return ProcessState{}, fmt.Errorf("ProcessInit: not a ServerBehavior") 185 } 186 ps := ProcessState{ 187 Process: p, 188 } 189 190 sp := &ServerProcess{ 191 ProcessState: ps, 192 behavior: behavior, 193 194 // callbackWaitReply must be defined here, otherwise making a Call request 195 // will not be able in the inherited object (locks on trying to send 196 // a message to the nil channel) 197 callbackWaitReply: make(chan *etf.Ref), 198 } 199 200 err := behavior.Init(sp, args...) 201 if err != nil { 202 return ProcessState{}, err 203 } 204 ps.State = sp 205 return ps, nil 206 } 207 208 // ProcessLoop 209 func (gs *Server) ProcessLoop(ps ProcessState, started chan<- bool) string { 210 sp, ok := ps.State.(*ServerProcess) 211 if !ok { 212 return "ProcessLoop: not a ServerBehavior" 213 } 214 215 channels := ps.ProcessChannels() 216 sp.mailbox = channels.Mailbox 217 sp.original = channels.Mailbox 218 sp.deferred = make(chan ProcessMailboxMessage, cap(channels.Mailbox)) 219 sp.currentFunction = "Server:loop" 220 sp.stop = make(chan string, 2) 221 222 defer func() { 223 if sp.waitReply == nil { 224 return 225 } 226 // there is running callback goroutine that waiting for a reply. to get rid 227 // of infinity lock (of this callback goroutine) we must provide a reader 228 // for the callbackWaitReply channel (it writes a nil value to this channel 229 // on exit) 230 go sp.waitCallbackOrDeferr(nil) 231 }() 232 233 started <- true 234 for { 235 var message etf.Term 236 var fromPid etf.Pid 237 238 select { 239 case ex := <-channels.GracefulExit: 240 if sp.TrapExit() == false { 241 sp.behavior.Terminate(sp, ex.Reason) 242 return ex.Reason 243 } 244 // Enabled trap exit message. Transform exit signal 245 // into MessageExit and send it to itself as a regular message 246 // keeping the processing order right. 247 // We should process this message after the others we got earlier 248 // from the died process. 249 message = MessageExit{ 250 Pid: ex.From, 251 Reason: ex.Reason, 252 } 253 // We can't write this message to the mailbox directly so use 254 // the common way to send it to itself 255 ps.Send(ps.Self(), message) 256 continue 257 258 case reason := <-sp.stop: 259 sp.behavior.Terminate(sp, reason) 260 return reason 261 262 case msg := <-sp.mailbox: 263 sp.mailbox = sp.original 264 fromPid = msg.From 265 message = msg.Message 266 267 case <-sp.Context().Done(): 268 sp.behavior.Terminate(sp, "kill") 269 return "kill" 270 271 case direct := <-channels.Direct: 272 sp.waitCallbackOrDeferr(direct) 273 continue 274 case sp.waitReply = <-sp.callbackWaitReply: 275 continue 276 } 277 278 lib.Log("[%s] GEN_SERVER %s got message from %s", sp.NodeName(), sp.Self(), fromPid) 279 280 switch m := message.(type) { 281 case etf.Tuple: 282 283 switch mtag := m.Element(1).(type) { 284 case etf.Ref: 285 // check if we waiting for reply 286 if len(m) != 2 { 287 break 288 } 289 sp.PutSyncReply(mtag, m.Element(2), nil) 290 if sp.waitReply != nil && *sp.waitReply == mtag { 291 sp.waitReply = nil 292 // continue read sp.callbackWaitReply channel 293 // to wait for the exit from the callback call 294 sp.waitCallbackOrDeferr(nil) 295 continue 296 } 297 298 case etf.Atom: 299 switch mtag { 300 case etf.Atom("$gen_call"): 301 302 var from ServerFrom 303 var ok bool 304 if len(m) != 3 { 305 // wrong $gen_call message. ignore it 306 break 307 } 308 309 fromTuple, ok := m.Element(2).(etf.Tuple) 310 if !ok || len(fromTuple) != 2 { 311 // not a tuple or has wrong value 312 break 313 } 314 315 from.Pid, ok = fromTuple.Element(1).(etf.Pid) 316 if !ok { 317 // wrong Pid value 318 break 319 } 320 321 correct := false 322 switch v := fromTuple.Element(2).(type) { 323 case etf.Ref: 324 from.Ref = v 325 correct = true 326 case etf.List: 327 var ok bool 328 // was sent with "alias" [etf.Atom("alias"), etf.Ref] 329 if len(v) != 2 { 330 // wrong value 331 break 332 } 333 if alias, ok := v.Element(1).(etf.Atom); !ok || alias != etf.Atom("alias") { 334 // wrong value 335 break 336 } 337 from.Ref, ok = v.Element(2).(etf.Ref) 338 if !ok { 339 // wrong value 340 break 341 } 342 from.ReplyByAlias = true 343 correct = true 344 } 345 346 if correct == false { 347 break 348 } 349 350 callMessage := handleCallMessage{ 351 from: from, 352 message: m.Element(3), 353 } 354 sp.waitCallbackOrDeferr(callMessage) 355 continue 356 357 case etf.Atom("$gen_cast"): 358 if len(m) != 2 { 359 // wrong $gen_cast message. ignore it 360 break 361 } 362 castMessage := handleCastMessage{ 363 message: m.Element(2), 364 } 365 sp.waitCallbackOrDeferr(castMessage) 366 continue 367 } 368 } 369 370 lib.Log("[%s] GEN_SERVER %#v got simple message %#v", sp.NodeName(), sp.Self(), message) 371 infoMessage := handleInfoMessage{ 372 message: message, 373 } 374 sp.waitCallbackOrDeferr(infoMessage) 375 376 case handleCallMessage: 377 sp.waitCallbackOrDeferr(message) 378 case handleCastMessage: 379 sp.waitCallbackOrDeferr(message) 380 case handleInfoMessage: 381 sp.waitCallbackOrDeferr(message) 382 case ProcessDirectMessage: 383 sp.waitCallbackOrDeferr(message) 384 385 default: 386 lib.Log("m: %#v", m) 387 infoMessage := handleInfoMessage{ 388 message: m, 389 } 390 sp.waitCallbackOrDeferr(infoMessage) 391 } 392 } 393 } 394 395 // ServerProcess handlers 396 397 func (sp *ServerProcess) waitCallbackOrDeferr(message interface{}) { 398 if sp.waitReply != nil { 399 // already waiting for reply. deferr this message 400 deferred := ProcessMailboxMessage{ 401 Message: message, 402 } 403 select { 404 case sp.deferred <- deferred: 405 // do nothing 406 default: 407 lib.Warning("deferred mailbox of %s[%q] is full. dropped message %v", 408 sp.Self(), sp.Name(), message) 409 } 410 411 return 412 } 413 414 switch m := message.(type) { 415 case handleCallMessage: 416 go func() { 417 sp.counter++ 418 sp.handleCall(m) 419 sp.callbackWaitReply <- nil 420 }() 421 case handleCastMessage: 422 go func() { 423 sp.counter++ 424 sp.handleCast(m) 425 sp.callbackWaitReply <- nil 426 }() 427 case handleInfoMessage: 428 go func() { 429 sp.counter++ 430 sp.handleInfo(m) 431 sp.callbackWaitReply <- nil 432 }() 433 case ProcessDirectMessage: 434 go func() { 435 sp.counter++ 436 sp.handleDirect(m) 437 sp.callbackWaitReply <- nil 438 }() 439 case nil: 440 // it was called just to read the channel sp.callbackWaitReply 441 442 default: 443 lib.Warning("unknown message type in waitCallbackOrDeferr: %#v", message) 444 return 445 } 446 447 select { 448 449 //case <-sp.Context().Done(): 450 // do not read the context state. otherwise the goroutine with running callback 451 // might lock forever on exit (or on making a Call request) as nobody read 452 // the callbackWaitReply channel. 453 454 case sp.waitReply = <-sp.callbackWaitReply: 455 // not nil value means callback made a Call request and waiting for reply 456 if sp.waitReply == nil && len(sp.deferred) > 0 { 457 sp.mailbox = sp.deferred 458 } 459 return 460 } 461 } 462 463 func (sp *ServerProcess) panicHandler() { 464 if r := recover(); r != nil { 465 pc, fn, line, _ := runtime.Caller(2) 466 lib.Warning("Server terminated %s[%q]. Panic reason: %#v at %s[%s:%d]", 467 sp.Self(), sp.Name(), r, runtime.FuncForPC(pc).Name(), fn, line) 468 sp.stop <- "panic" 469 } 470 } 471 472 func (sp *ServerProcess) handleDirect(direct ProcessDirectMessage) { 473 if lib.CatchPanic() { 474 defer sp.panicHandler() 475 } 476 477 cf := sp.currentFunction 478 sp.currentFunction = "Server:HandleDirect" 479 reply, status := sp.behavior.HandleDirect(sp, direct.Ref, direct.Message) 480 sp.currentFunction = cf 481 switch status { 482 case DirectStatusIgnore: 483 return 484 default: 485 sp.PutSyncReply(direct.Ref, reply, status) 486 } 487 } 488 489 func (sp *ServerProcess) handleCall(m handleCallMessage) { 490 if lib.CatchPanic() { 491 defer sp.panicHandler() 492 } 493 494 cf := sp.currentFunction 495 sp.currentFunction = "Server:HandleCall" 496 reply, status := sp.behavior.HandleCall(sp, m.from, m.message) 497 sp.currentFunction = cf 498 switch status { 499 case ServerStatusOK: 500 sp.SendReply(m.from, reply) 501 case ServerStatusIgnore: 502 return 503 case ServerStatusStop: 504 sp.stop <- "normal" 505 506 default: 507 sp.stop <- status.Error() 508 } 509 } 510 511 func (sp *ServerProcess) handleCast(m handleCastMessage) { 512 if lib.CatchPanic() { 513 defer sp.panicHandler() 514 } 515 516 cf := sp.currentFunction 517 sp.currentFunction = "Server:HandleCast" 518 status := sp.behavior.HandleCast(sp, m.message) 519 sp.currentFunction = cf 520 521 switch status { 522 case ServerStatusOK, ServerStatusIgnore: 523 return 524 case ServerStatusStop: 525 sp.stop <- "normal" 526 default: 527 sp.stop <- status.Error() 528 } 529 } 530 531 func (sp *ServerProcess) handleInfo(m handleInfoMessage) { 532 if lib.CatchPanic() { 533 defer sp.panicHandler() 534 } 535 536 cf := sp.currentFunction 537 sp.currentFunction = "Server:HandleInfo" 538 status := sp.behavior.HandleInfo(sp, m.message) 539 sp.currentFunction = cf 540 switch status { 541 case ServerStatusOK, ServerStatusIgnore: 542 return 543 case ServerStatusStop: 544 sp.stop <- "normal" 545 default: 546 sp.stop <- status.Error() 547 } 548 } 549 550 // 551 // default callbacks for Server interface 552 // 553 554 // Init 555 func (gs *Server) Init(process *ServerProcess, args ...etf.Term) error { 556 return nil 557 } 558 559 // HanldeCast 560 func (gs *Server) HandleCast(process *ServerProcess, message etf.Term) ServerStatus { 561 lib.Warning("Server [%s] HandleCast: unhandled message %#v", process.Name(), message) 562 return ServerStatusOK 563 } 564 565 // HandleInfo 566 func (gs *Server) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) { 567 lib.Warning("Server [%s] HandleCall: unhandled message %#v from %#v", process.Name(), message, from) 568 return "ok", ServerStatusOK 569 } 570 571 // HandleDirect 572 func (gs *Server) HandleDirect(process *ServerProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) { 573 return nil, lib.ErrUnsupportedRequest 574 } 575 576 // HandleInfo 577 func (gs *Server) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus { 578 lib.Warning("Server [%s] HandleInfo: unhandled message %#v", process.Name(), message) 579 return ServerStatusOK 580 } 581 582 // Terminate 583 func (gs *Server) Terminate(process *ServerProcess, reason string) { 584 return 585 }