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 }