github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/actor/mailbox.go (about)

     1  package actor
     2  
     3  import (
     4  	"runtime"
     5  	"sync/atomic"
     6  
     7  	"github.com/asynkron/protoactor-go/internal/queue/mpsc"
     8  )
     9  
    10  // MailboxMiddleware is an interface for intercepting messages and events in the mailbox
    11  type MailboxMiddleware interface {
    12  	MailboxStarted()
    13  	MessagePosted(message interface{})
    14  	MessageReceived(message interface{})
    15  	MailboxEmpty()
    16  }
    17  
    18  // MessageInvoker is the interface used by a mailbox to forward messages for processing
    19  type MessageInvoker interface {
    20  	InvokeSystemMessage(interface{})
    21  	InvokeUserMessage(interface{})
    22  	EscalateFailure(reason interface{}, message interface{})
    23  }
    24  
    25  // Mailbox interface is used to enqueue messages to the mailbox
    26  type Mailbox interface {
    27  	PostUserMessage(message interface{})
    28  	PostSystemMessage(message interface{})
    29  	RegisterHandlers(invoker MessageInvoker, dispatcher Dispatcher)
    30  	Start()
    31  	UserMessageCount() int
    32  }
    33  
    34  // MailboxProducer is a function which creates a new mailbox
    35  type MailboxProducer func() Mailbox
    36  
    37  const (
    38  	idle int32 = iota
    39  	running
    40  )
    41  
    42  type defaultMailbox struct {
    43  	userMailbox     queue
    44  	systemMailbox   *mpsc.Queue
    45  	schedulerStatus int32
    46  	userMessages    int32
    47  	sysMessages     int32
    48  	suspended       int32
    49  	invoker         MessageInvoker
    50  	dispatcher      Dispatcher
    51  	middlewares     []MailboxMiddleware
    52  }
    53  
    54  func (m *defaultMailbox) PostUserMessage(message interface{}) {
    55  	// is it a raw batch message?
    56  	if batch, ok := message.(MessageBatch); ok {
    57  		messages := batch.GetMessages()
    58  
    59  		for _, msg := range messages {
    60  			m.PostUserMessage(msg)
    61  		}
    62  	}
    63  
    64  	// is it an envelope batch message?
    65  	// FIXME: check if this is still needed, maybe MessageEnvelope can only exist as a pointer
    66  	if env, ok := message.(MessageEnvelope); ok {
    67  		if batch, ok := env.Message.(MessageBatch); ok {
    68  			messages := batch.GetMessages()
    69  
    70  			for _, msg := range messages {
    71  				m.PostUserMessage(msg)
    72  			}
    73  		}
    74  	}
    75  	if env, ok := message.(*MessageEnvelope); ok {
    76  		if batch, ok := env.Message.(MessageBatch); ok {
    77  			messages := batch.GetMessages()
    78  
    79  			for _, msg := range messages {
    80  				m.PostUserMessage(msg)
    81  			}
    82  		}
    83  	}
    84  
    85  	// normal messages
    86  	for _, ms := range m.middlewares {
    87  		ms.MessagePosted(message)
    88  	}
    89  	m.userMailbox.Push(message)
    90  	atomic.AddInt32(&m.userMessages, 1)
    91  	m.schedule()
    92  }
    93  
    94  func (m *defaultMailbox) PostSystemMessage(message interface{}) {
    95  	for _, ms := range m.middlewares {
    96  		ms.MessagePosted(message)
    97  	}
    98  	m.systemMailbox.Push(message)
    99  	atomic.AddInt32(&m.sysMessages, 1)
   100  	m.schedule()
   101  }
   102  
   103  func (m *defaultMailbox) RegisterHandlers(invoker MessageInvoker, dispatcher Dispatcher) {
   104  	m.invoker = invoker
   105  	m.dispatcher = dispatcher
   106  }
   107  
   108  func (m *defaultMailbox) schedule() {
   109  	if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
   110  		m.dispatcher.Schedule(m.processMessages)
   111  	}
   112  }
   113  
   114  func (m *defaultMailbox) processMessages() {
   115  process:
   116  	m.run()
   117  
   118  	// set mailbox to idle
   119  	atomic.StoreInt32(&m.schedulerStatus, idle)
   120  	sys := atomic.LoadInt32(&m.sysMessages)
   121  	user := atomic.LoadInt32(&m.userMessages)
   122  	// check if there are still messages to process (sent after the message loop ended)
   123  	if sys > 0 || (atomic.LoadInt32(&m.suspended) == 0 && user > 0) {
   124  		// try setting the mailbox back to running
   125  		if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
   126  			//	fmt.Printf("looping %v %v %v\n", sys, user, m.suspended)
   127  			goto process
   128  		}
   129  	}
   130  
   131  	for _, ms := range m.middlewares {
   132  		ms.MailboxEmpty()
   133  	}
   134  }
   135  
   136  func (m *defaultMailbox) run() {
   137  	var msg interface{}
   138  
   139  	defer func() {
   140  		if r := recover(); r != nil {
   141  			m.invoker.EscalateFailure(r, msg)
   142  		}
   143  	}()
   144  
   145  	i, t := 0, m.dispatcher.Throughput()
   146  	for {
   147  		if i > t {
   148  			i = 0
   149  			runtime.Gosched()
   150  		}
   151  
   152  		i++
   153  
   154  		// keep processing system messages until queue is empty
   155  		if msg = m.systemMailbox.Pop(); msg != nil {
   156  			atomic.AddInt32(&m.sysMessages, -1)
   157  			switch msg.(type) {
   158  			case *SuspendMailbox:
   159  				atomic.StoreInt32(&m.suspended, 1)
   160  			case *ResumeMailbox:
   161  				atomic.StoreInt32(&m.suspended, 0)
   162  			default:
   163  				m.invoker.InvokeSystemMessage(msg)
   164  			}
   165  			for _, ms := range m.middlewares {
   166  				ms.MessageReceived(msg)
   167  			}
   168  			continue
   169  		}
   170  
   171  		// didn't process a system message, so break until we are resumed
   172  		if atomic.LoadInt32(&m.suspended) == 1 {
   173  			return
   174  		}
   175  
   176  		if msg = m.userMailbox.Pop(); msg != nil {
   177  			atomic.AddInt32(&m.userMessages, -1)
   178  			m.invoker.InvokeUserMessage(msg)
   179  			for _, ms := range m.middlewares {
   180  				ms.MessageReceived(msg)
   181  			}
   182  		} else {
   183  			return
   184  		}
   185  	}
   186  }
   187  
   188  func (m *defaultMailbox) Start() {
   189  	for _, ms := range m.middlewares {
   190  		ms.MailboxStarted()
   191  	}
   192  }
   193  
   194  func (m *defaultMailbox) UserMessageCount() int {
   195  	return int(atomic.LoadInt32(&m.userMessages))
   196  }