KeyedExecutor

class KeyedExecutor(val workers: Int = DEFAULT_WORKERS, val maxInFlight: Int = DEFAULT_MAX_IN_FLIGHT, val name: String = DEFAULT_NAME, val throughput: Int = DEFAULT_THROUGHPUT, val spin: Duration = DEFAULT_SPIN) : AutoCloseable

The execution resource every dispatcher of one me.ahoo.wow.runtime.WowRuntime shares (design X7): one set of workers threads, sized by CPU cores rather than by the number of aggregate types or dispatchers.

A dispatcher keeps one mailbox per aggregate ID. A mailbox runs its messages one at a time, in arrival order, on the worker it was given when it was created (a running worker with nothing queued, else a parked one); different mailboxes run in parallel. Each worker has its own FIFO queue, so a mailbox that has run throughput messages goes behind the other mailboxes of its worker. A handler that waits (I/O, a retry backoff) holds no worker and delays only its own mailbox. Each receiver (a dispatcher has one per bounded context) accepts at most maxInFlight messages it has not finished, and requests more from its transport only as they finish, so a slow dispatcher backpressures its source instead of buffering.

A suspend or Flow message function called by a dispatcher resumes on these workers (coroutineDispatcher) instead of Dispatchers.Default; its mailbox does not start the next message until it returns, so per-aggregate serialization holds across suspension points.

The runtime owns its executor and disposes it once every component has stopped.

Parameters

workers

the number of worker threads; defaults to the available processors.

maxInFlight

the most messages one receiver, i.e. one (dispatcher, bounded context), holds unfinished (running or queued in a mailbox).

name

the worker thread name prefix.

throughput

the most messages of one aggregate a worker runs in one turn (while they complete synchronously) before it moves on to other aggregates, so one hot aggregate cannot starve the others.

spin

the longest a worker that ran out of messages spins before it parks, at most MAX_SPIN; the default, Duration.ZERO, parks at once (no spin). Worth setting only for in-memory, high-rate dispatch whose messages keep arriving within microseconds of each other; with a real store the gain is within noise. A worker spins only when its last wait was shorter than this; after a longer wait it parks at once. A spinning worker takes the next message without the wake-up a park costs (an OS call by the sender and the scheduling delay of the woken thread). CPU cost: while messages keep arriving at intervals shorter than the spin, a worker can stay busy on one core the whole time; after each short interval, the next wait can cost up to the spin in CPU even when no message follows. The worst case is workers cores. In a container with a CPU quota (cgroup limit), set it to Duration.ZERO.

Constructors

Link copied to clipboard
constructor(workers: Int = DEFAULT_WORKERS, maxInFlight: Int = DEFAULT_MAX_IN_FLIGHT, name: String = DEFAULT_NAME, throughput: Int = DEFAULT_THROUGHPUT, spin: Duration = DEFAULT_SPIN)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val coroutineDispatcher: CoroutineDispatcher

The workers as a coroutine dispatcher, for suspend and Flow message functions.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Functions

Link copied to clipboard
open override fun close()

Stops the workers once their queued runs finish; a run submitted afterwards is rejected. Idempotent.

Link copied to clipboard

Stops the workers at once (the runtime's force stop, like disposing a Reactor scheduler): queued mailboxes are discarded on the calling thread — their messages are rejected and stay unacknowledged for redelivery — and no mailbox starts another message. A handler already running finishes its current step. Idempotent.

Link copied to clipboard
open override fun toString(): String