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  }