Skip to content

Use stm-queue's bounded queues, add ActorConfig, monitors, and awaitEffects - #15

Open
SamuelSchlesinger wants to merge 2 commits into
mainfrom
use-stm-queue-bounded
Open

Use stm-queue's bounded queues, add ActorConfig, monitors, and awaitEffects#15
SamuelSchlesinger wants to merge 2 commits into
mainfrom
use-stm-queue-bounded

Conversation

@SamuelSchlesinger

@SamuelSchlesinger SamuelSchlesinger commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Two commits, releasing as 0.4.0.0 (Hackage has 0.3.1.1; 0.4.0.0 was never published, so the changelog entry is dated rather than bumped further).

1. Bounded mailboxes delegate to stm-queue (commit 1)

Bounded mailboxes were an unbounded stm-queue plus a single occupancy TVar that every send and every receive wrote. stm-queue-0.2.2.0 (stm-queue#3) provides bounded queues with split read/write credits, so capacity accounting conflicts once per capacity sends instead of on every message. The in-house Mailbox record and counter are deleted; semantics are unchanged and covered by the existing tests plus a new 8-sender × 500-message fan-in test.

2. API improvements from the review (commit 2)

  • actWith + ActorConfig (mailboxCapacity, onCompletion, onUndelivered, onEffectFailure), with defaultActorConfig. act/actBounded/actFinally/actFinallyBounded are specialisations, unchanged in signature.
  • Dead letters: messages still queued when an actor stops go to onUndelivered in mailbox order (only called when non-empty). Every committed send now reaches either a handler or onUndelivered.
  • Effect failures: each throwing completion effect is passed to onEffectFailure; the default rethrows, preserving the old "first exception rethrown in the terminating thread" behaviour, but a logging handler can now replace stderr output.
  • addAfterEffect is lifecycle-checked by default, matching send. addAfterEffectUnchecked keeps the old behaviour; addAfterEffectChecked stays as an alias.
  • monitor / monitorSTM: message-based completion notification (Erlang 'DOWN'-style) alongside exception-based link. Immediate delivery for an already-stopped target, transactional with the registration.
  • awaitEffects: waits until the completion handler and after-effects have finished (set in a finally, so it fires even if effects throw).
  • murder is a no-op once the actor has stopped, so cleanup is never interrupted. (A murder racing the transition is caught by the effect runner and recorded as an effect failure — cleanup was never truncated, contrary to what the review first suggested.)
  • GC behaviour documented and tested: a receiver whose handles are all dropped is stopped with BlockedIndefinitelyOnSTM, cascading through links/monitors.
  • Benchmark stm-actor-benchmark [senders...]: fan-in throughput, unbounded vs bounded mailbox. CI now builds it under -Werror.

Effect order at shutdown: lifecycle transition → links/monitors initiated → onCompletiononUndelivered → user after-effects → effectsFinished set.

Release ordering

stm-queue-0.2.2.0 is not on Hackage yet, so cabal.project pins it to commit b1c8f56. The main CI jobs pass with the pin; the sdist-outside-project and oldest-dependencies jobs will fail until it is published — that is what they are for. Order: merge stm-queue#3 → publish stm-queue 0.2.2.0 → drop the pin → merge this.

Test plan

  • cabal build --enable-tests --enable-benchmarks all --ghc-options=-Werror (pinned project and a local checkout)
  • cabal test all — 46 examples, 0 failures; 6 consecutive runs across -N2/-N4, including the GC test
  • cabal check and cabal haddock --haddock-for-hackage clean
  • Benchmark runs; bounded figures under fan-in are dominated by STM wakeup storms and are sensitive to -N (noted in the source)

stm-actor implemented bounded mailboxes as an unbounded stm-queue plus a
single occupancy TVar that every send and every receive wrote, so senders
and the actor conflicted on every message. stm-queue-0.2.2.0 provides
bounded queues whose free capacity is tracked as split read and write
credits, so capacity accounting conflicts once per `capacity` sends
instead. Delegate to those queues and drop the in-house Mailbox record:
enqueue retries only on a full bounded queue, tryEnqueue never retries,
dequeue releases capacity, and flush releases all of it.

Construct mailboxes with newQueueIO and newBoundedQueueIO instead of an
atomically block. Require stm-queue >= 0.2.2.0, pinned to its git commit
until it is published on Hackage. Add a multi-sender bounded mailbox test.
…defaults

Introduce actWith and ActorConfig so an actor's mailbox capacity, completion
handler, undelivered-message handler, and effect-failure handler are
configured in one place; act, actBounded, actFinally, and actFinallyBounded
become specialisations. Messages still queued when an actor stops are handed
to onUndelivered in mailbox order instead of being discarded silently, and
each completion effect that throws is reported to onEffectFailure, which
rethrows by default to preserve the previous behaviour.

Make addAfterEffect lifecycle-checked by default, matching send, with
addAfterEffectUnchecked for callers that control the lifecycle. Add monitor
and monitorSTM, which deliver an actor's completion to the monitoring actor's
mailbox as an ordinary message rather than an asynchronous exception, and
notify immediately about an already-stopped target. Add awaitEffects, which
waits for the completion handler and after-effects to finish. Make murder a
no-op once the actor has stopped so cleanup is not interrupted.

Document that a receiver whose handles have all been dropped is stopped by
the runtime with BlockedIndefinitelyOnSTM, and test it. Add a fan-in
throughput benchmark comparing unbounded and bounded mailboxes. Date the
0.4.0.0 changelog entry for release.
@SamuelSchlesinger SamuelSchlesinger changed the title Use stm-queue's bounded queues for bounded mailboxes Use stm-queue's bounded queues, add ActorConfig, monitors, and awaitEffects Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant