Mailbox

scalus.cardano.node.stream.internal.Mailbox
See theMailbox companion object
sealed trait Mailbox[A] extends ScalusAsyncSource[A]

Producer-synchronous, consumer-asynchronous bridge between a provider and a subscriber's stream.

Two shapes, because two kinds of stream want opposite things on overflow:

  • DeltaMailbox — FIFO for state-mutating events. Silent drops are never acceptable: a missed Spent corrupts the subscriber's view permanently and invisibly. A bounded buffer that fills therefore fails the subscription rather than dropping.
  • LatestValueMailbox — size-1, newer wins, for single-source-of-truth cells (tip, params, transaction status). Here dropping intermediate values is precisely correct.

Not public API: this is one implementation of ScalusAsyncSource, and adapters are written against that interface so the buffering strategy stays free to change.

Buffering and delivery are separable, on purpose

offer is buffer-then-deliver, and a producer that needs several events to land atomically relative to its own state can split the two: offerBuffered under its lock, flush after releasing it. That is what lets the hub register a subscription and enqueue its seed as one indivisible step without ever running a consumer's continuation while holding the hub monitor.

Nothing observable escapes during the buffered phase — no promise is completed, no onCancel hook runs. Both are deferred to flush.

Termination always unregisters

Every way a mailbox can die — explicit cancel, clean close, producer fail, or a bounded buffer overflowing — fires onCancel exactly once. A subscription that ends by any route must stop costing the provider work; the alternative is a dead subscription matched against every block for the lifetime of the process.

Attributes

Companion
object
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Value members

Abstract methods

def close(): Unit

Signal clean end of stream. Idempotent.

Signal clean end of stream. Idempotent.

Attributes

def fail(t: Throwable): Unit

Signal producer failure. Idempotent.

Signal producer failure. Idempotent.

Attributes

def flush(): Unit

Deliver whatever offerBuffered left pending: complete a waiting pull, and fire onCancel if the mailbox died while buffering. Idempotent and cheap when there is nothing to do.

Deliver whatever offerBuffered left pending: complete a waiting pull, and fire onCancel if the mailbox died while buffering. Idempotent and cheap when there is nothing to do.

Attributes

def isClosed: Boolean
def offer(a: A): Unit

Enqueue the next value and deliver it. A no-op once closed, failed or cancelled.

Enqueue the next value and deliver it. A no-op once closed, failed or cancelled.

Attributes

def offerBuffered(a: A): Unit

Enqueue without delivering: completes no promise and fires no hook. Must be followed by flush, which is what makes the value visible.

Enqueue without delivering: completes no promise and fires no hook. Must be followed by flush, which is what makes the value visible.

Attributes

Inherited methods

final def pull(): Future[Option[A]]

Pull with nothing to cancel on. Convenient for synchronous callers and tests; anything that may need to interrupt a parked wait should pass a real token.

Pull with nothing to cancel on. Convenient for synchronous callers and tests; anything that may need to interrupt a parked wait should pass a real token.

Attributes

Inherited from:
ScalusAsyncSource

Inherited and Abstract methods

def cancel(): Unit

No further pull calls will happen — the stream was cancelled or the consumer finished. Unregisters the subscription. Idempotent.

No further pull calls will happen — the stream was cancelled or the consumer finished. Unregisters the subscription. Idempotent.

Attributes

Inherited from:
ScalusAsyncSource
def pull(cancelToken: CancelToken): Future[Option[A]]

Pull the next signal, abortable through cancelToken.

Pull the next signal, abortable through cancelToken.

  • Future.successful(Some(a)) — the next value
  • Future.successful(None) — clean end of stream
  • failed Future — the producer failed, a bounded buffer overflowed, or the token fired (a scalus.cardano.infra.CancelledException)

The token is how a Future-returning API becomes abortable at all. Future has no cancellation of its own, so an effect system that needs to interrupt a parked pull — an fs2 .timeout, a cancelled Resource.use, a runtime shutting down — has nothing to pull the plug on unless the capability is passed in. Adapters bridge their own cancellation onto this token; without it they can only mask cancellation and hang.

Cancelling a pull does not end the subscription: the next pull starts a fresh wait. cancel is what ends the subscription.

Single-consumer: exactly one caller pulls at a time, per source. Pulling again while a pull is outstanding returns the same future — so cancelling one cancels both.

Attributes

Inherited from:
ScalusAsyncSource