70 lines
1.5 KiB
Go
70 lines
1.5 KiB
Go
package actor
|
|
|
|
import (
|
|
"runtime"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// mailbox owns the single-consumer handoff and scheduling policy. running also
|
|
// covers the interval after scheduling and before the task starts executing.
|
|
type mailbox[T any] struct {
|
|
queue *queue[T]
|
|
invoker invoker[T]
|
|
dispatcher Dispatcher
|
|
task func()
|
|
batchSize int
|
|
yield func()
|
|
running atomic.Bool
|
|
}
|
|
|
|
func newMailbox[T any](q *queue[T], invoke invoker[T], batchSize int, dispatcher Dispatcher) *mailbox[T] {
|
|
m := &mailbox[T]{
|
|
queue: q,
|
|
invoker: invoke,
|
|
dispatcher: dispatcher,
|
|
batchSize: batchSize,
|
|
yield: runtime.Gosched,
|
|
}
|
|
// Reuse this method value for every scheduling request, without allocating
|
|
// a closure on each Send or ownership handoff.
|
|
m.task = m.drain
|
|
return m
|
|
}
|
|
|
|
func (m *mailbox[T]) send(message T) {
|
|
m.queue.push(message)
|
|
m.tryRun()
|
|
}
|
|
|
|
func (m *mailbox[T]) tryRun() {
|
|
if m.running.CompareAndSwap(false, true) {
|
|
m.dispatcher.Schedule(m.task)
|
|
}
|
|
}
|
|
|
|
func (m *mailbox[T]) drain() {
|
|
processed := 0
|
|
for {
|
|
batch := m.queue.takeBatch(m.batchSize, m.yield)
|
|
if batch == nil {
|
|
m.running.Store(false)
|
|
// A producer may have published while running was still true.
|
|
// Either we reclaim ownership or that producer schedules a task.
|
|
if m.queue.empty() || !m.running.CompareAndSwap(false, true) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
for n := batch; n != nil; {
|
|
message, next := m.queue.pop(n)
|
|
n = next
|
|
m.invoker.receive(message)
|
|
processed++
|
|
if processed == m.batchSize {
|
|
processed = 0
|
|
m.yield()
|
|
}
|
|
}
|
|
}
|
|
}
|