Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
221 commits
Select commit Hold shift + click to select a range
5bb51bc
feat: deferred replies and pipe-to-self (rank 11), WIP
unbalancedparentheses Nov 10, 2026
847cfe8
feat(reply): packed-word reply slot state machine with Loom model
unbalancedparentheses Nov 10, 2026
877989e
test(reply): enable loom futures for the polled receiver
unbalancedparentheses Nov 10, 2026
50888a8
chore: lock pin-utils for loom futures
unbalancedparentheses Nov 10, 2026
f2630f8
test(reply): close must leave the slot terminal
unbalancedparentheses Nov 10, 2026
bc46ffc
ci: run the reply slot Loom model
unbalancedparentheses Nov 10, 2026
1c5d918
feat: reply slots on the request path, PendingReply/ReplyTo, handler …
unbalancedparentheses Nov 10, 2026
f4a9815
refactor: migrate manual Handler impls to the PendingReply signature …
unbalancedparentheses Nov 10, 2026
0d4386e
wip
unbalancedparentheses Nov 10, 2026
997558e
style: rustfmt
unbalancedparentheses Nov 10, 2026
57aeaff
fix: adapt tests, benches and examples to ReplyReceiver
unbalancedparentheses Nov 10, 2026
f859b67
fix: unreachable replies after panics in migrated handlers
unbalancedparentheses Nov 10, 2026
34d3a26
perf: do not keep the request Result alive across the reply await
unbalancedparentheses Nov 10, 2026
84f3f98
test(ui): deferred handler pass case and misuse diagnostics
unbalancedparentheses Nov 10, 2026
89959c8
fix(macros): handlers returning impl Future are awaited before replying
unbalancedparentheses Nov 10, 2026
a41df96
test(ui): regenerate macro diagnostics for the new Handler signature
unbalancedparentheses Nov 10, 2026
ef49870
test: deferred reply discharge paths in both modes; ctx.deferred_outs…
unbalancedparentheses Nov 10, 2026
ba978cf
style: rustfmt
unbalancedparentheses Nov 10, 2026
d70bad2
feat(reply): per-actor deadline heap and deadline service (thread; pe…
unbalancedparentheses Nov 10, 2026
f678aa5
perf(reply): cheaper turn phase and no clock read in the async receiv…
unbalancedparentheses Nov 10, 2026
aa8f019
feat(jobs): tasks-mode pipe, pipe_keyed, pipe_with, pipe_blocking_wit…
unbalancedparentheses Nov 10, 2026
6c80bca
fix(jobs): rename the pipe_reply link helper
unbalancedparentheses Nov 10, 2026
e20b3dd
feat(jobs): threads-mode pipe_blocking_with and pipe_reply_blocking_w…
unbalancedparentheses Nov 10, 2026
9478d37
feat(jobs): threads-mode pipe_blocking_with and pipe_reply_blocking_w…
unbalancedparentheses Nov 10, 2026
e256f90
test: pipe-to-self jobs (results, panics, keyed fencing, deadlines, e…
unbalancedparentheses Nov 10, 2026
0142c5a
feat(reply): PendingReplies<K, R> bounded token table
unbalancedparentheses Nov 10, 2026
7e272fe
chore: cfg-gate items unused in sim or thread-only builds
unbalancedparentheses Nov 10, 2026
524f63c
refactor: tidy migrated handlers (reply after the body, no wrapper bl…
unbalancedparentheses Nov 10, 2026
eb5878e
fix: tidy leftovers (store actor, panic example), test cfgs
unbalancedparentheses Nov 10, 2026
6da7347
fix(tests): metrics test uses send-time deadlines; rustfmt
unbalancedparentheses Nov 10, 2026
ef37178
style: rustfmt
unbalancedparentheses Nov 10, 2026
8496cc4
docs: deferred replies guide section, semantics R1/R3/R5-R7, migratio…
unbalancedparentheses Nov 10, 2026
4524afd
docs: changelog for rank 11; example index
unbalancedparentheses Nov 10, 2026
edd572c
perf(reply): one-pointer ReplyReceiver; the deadline timer lives in t…
unbalancedparentheses Nov 11, 2026
5088047
bench: await the receiver via IntoFuture
unbalancedparentheses Nov 11, 2026
414482c
feat: cleanup deadline from supervisor shutdown_timeout / system grac…
unbalancedparentheses Nov 11, 2026
565b24d
fix(review): credit guard, keyed fencing after completion, destructio…
unbalancedparentheses Nov 11, 2026
b1d9b98
fix: caller-give-up cancellation for blocking linked jobs; inbox imports
unbalancedparentheses Nov 11, 2026
0c0d116
test(review): regression tests for credit guard, finished keyed fenci…
unbalancedparentheses Nov 11, 2026
2657a41
test: keyed-fencing regression without blocking the job's worker
unbalancedparentheses Nov 11, 2026
7945679
test: gate the old keyed job so its terminal message queues behind th…
unbalancedparentheses Nov 11, 2026
8f80adb
fix(review agy): cas re-evaluates a changed word before reporting the…
unbalancedparentheses Nov 11, 2026
529fd22
fix(slot): defer at the bound reports Closed when the caller left fir…
unbalancedparentheses Nov 11, 2026
581e7de
docs(design): implementation notes for #239
unbalancedparentheses Nov 11, 2026
c134b6f
perf(reply): raw receivers put their deadline in the owner's heap ins…
unbalancedparentheses Nov 11, 2026
f47ddb2
fix: bounds for the owner-heap receiver path
unbalancedparentheses Nov 11, 2026
85ff997
perf: the slot carries its owner; ReplyReceiver stays one pointer
unbalancedparentheses Nov 11, 2026
9ab004c
chore: allow the ownerless slot constructor
unbalancedparentheses Nov 11, 2026
ddff7f1
perf(registry): keep the turn phase off the Arc-count cache line
unbalancedparentheses Nov 11, 2026
f299b82
fix: test races on the handler's record of Overloaded; clippy (test m…
unbalancedparentheses Nov 11, 2026
65d57f3
test: read the handler's Overloaded record after a later turn
unbalancedparentheses Nov 11, 2026
200cff6
refactor(registry): turn phase in its own module; Loom model of drop …
unbalancedparentheses Nov 11, 2026
f93688a
style: cargo fmt
unbalancedparentheses Nov 11, 2026
a1ed0fc
test(jobs): job core extracted for Loom; model admission rollback, co…
unbalancedparentheses Nov 11, 2026
216a0a1
verify(jobs): lifecycle-trace events for the job core, TraceJobs.tla …
unbalancedparentheses Nov 11, 2026
e68ed39
test(jobs): name the trace test handoff type
unbalancedparentheses Nov 11, 2026
954cadb
verify(replies): lifecycle-trace events for reply slots, TraceReplies…
unbalancedparentheses Nov 11, 2026
009355d
feat(metrics): spawned_deferred_* and spawned_jobs_* metric families
unbalancedparentheses Nov 11, 2026
56f061e
test(sim): deferred reply and job races under PCT and bounded DFS
unbalancedparentheses Nov 11, 2026
80e9562
fix(trace): record deferred reply and job events only for watched owners
unbalancedparentheses Nov 11, 2026
117e95f
Merge origin/main (exit-signals design) into feat/otp-exit-signals
unbalancedparentheses Nov 11, 2026
176d924
feat(exit): exit request with kill provenance, death point, Killed/No…
unbalancedparentheses Nov 11, 2026
8ca2940
wip
unbalancedparentheses Nov 11, 2026
b18231b
feat(test): faults::exit sends an exact exit signal
unbalancedparentheses Nov 11, 2026
324f629
test: adapt unit tests to the death point and Killed; invert the late…
unbalancedparentheses Nov 11, 2026
ad60bbf
wip
unbalancedparentheses Nov 11, 2026
79e1951
test(shards): ReplyReceiver::try_recv returns Option (rank 11 API)
unbalancedparentheses Nov 11, 2026
3958d90
test: update lifecycle tests for Killed, NoProc, cleanup failures and…
unbalancedparentheses Nov 11, 2026
2a6774a
test: monitors past the death point get NoProc; stopped() panic repla…
unbalancedparentheses Nov 11, 2026
5030b42
feat(test)!: spawned-test exit injection follows exit_signal/2; updat…
unbalancedparentheses Nov 11, 2026
f60f5b8
feat(supervision): supervisor and system shutdown send the parent exi…
unbalancedparentheses Nov 11, 2026
7fd4a13
feat(link): link from an actor whose exit began is inert; tests and s…
unbalancedparentheses Nov 11, 2026
56ce637
test(link): exiting caller's link is inert
unbalancedparentheses Nov 11, 2026
9360d45
docs: CHANGELOG and MIGRATION for exit signals (in progress)
unbalancedparentheses Nov 11, 2026
ea8b838
docs: supervisor/system E3 reverted pending test adaptation
unbalancedparentheses Nov 11, 2026
2ca44ba
style: cargo fmt
unbalancedparentheses Nov 11, 2026
2fa4ca0
test: name the action type
unbalancedparentheses Nov 11, 2026
4facf6b
docs: supervised actors skip stopped() on shutdown unless trapping ex…
unbalancedparentheses Nov 11, 2026
daef3c3
feat(supervision): supervisor and system shutdown send the parent exi…
unbalancedparentheses Nov 11, 2026
8beb703
test: shutdown-order actors trap exits (E3); kill at grace yields Kil…
unbalancedparentheses Nov 11, 2026
73289af
fix(system): kill remaining actors only at grace expiry; test updates
unbalancedparentheses Nov 11, 2026
09ea3fb
test(cancellation): zero-grace shutdown kills a stuck stopped()
unbalancedparentheses Nov 11, 2026
c5690d9
test: supervisor shutdown as the parent's exit signal (E3)
unbalancedparentheses Nov 11, 2026
a5b67d7
docs: E3 supervisor/system shutdown is in
unbalancedparentheses Nov 11, 2026
60ae442
style: cargo fmt
unbalancedparentheses Nov 11, 2026
22a18db
feat(supervision): S1/S2 startup as in OTP (rank 5f)
unbalancedparentheses Nov 11, 2026
43aba08
feat(reply): ActorStopped carries the final reason, NoProc after deat…
unbalancedparentheses Nov 11, 2026
54e5549
test: let the system kill land before releasing started() in expired_…
unbalancedparentheses Nov 11, 2026
ada6ec4
feat(trace): lifecycle-trace mapping of real runs to Lifecycle.tla (r…
unbalancedparentheses Nov 11, 2026
0f844b7
feat(reply): client-port API refinements for rank 11
unbalancedparentheses Nov 11, 2026
576d7f3
feat(exit): client-port feedback: error classification, stopped() ski…
unbalancedparentheses Nov 11, 2026
a54b6ee
docs/test: client-port refinements in GUIDE, MIGRATION, CHANGELOG, de…
unbalancedparentheses Nov 11, 2026
a102947
test(exit): Loom model of the link table and monitor fence, simulator…
unbalancedparentheses Nov 11, 2026
7a709cd
docs(exit): GUIDE exit-signal section, exit_signals example, semantic…
unbalancedparentheses Nov 11, 2026
a54b8df
Merge feat/deferred-replies (#239 client-port refinements) into feat/…
unbalancedparentheses Nov 11, 2026
c3f5a5c
test(exit): per-thread outcome counts in exit_signals_sim
unbalancedparentheses Nov 11, 2026
0fe28e0
test(exit): compile otp_exit_signals out under spawned_sim
unbalancedparentheses Nov 11, 2026
600f984
test(jobs): hold the replacing turn with an await, not a blocked worker
unbalancedparentheses Nov 11, 2026
e0f1785
test(sim): adapt the simulator suites to E3 and S1
unbalancedparentheses Nov 11, 2026
850fd78
test(shutdown): do not rely on stopped() starting before a zero-grace…
unbalancedparentheses Nov 11, 2026
5e20f0f
Merge feat/deferred-replies (600f9846) into feat/otp-exit-signals
unbalancedparentheses Nov 11, 2026
8a9cba7
fix(jobs): discard a cancelled emit_at registration without self-dead…
unbalancedparentheses Nov 11, 2026
8730a67
fix(jobs): return a refused emit's credit once, through its guard
unbalancedparentheses Nov 11, 2026
903be60
fix(tasks): catch a panic while building a handler's future as a hand…
unbalancedparentheses Nov 11, 2026
49ce753
fix(jobs): a result held for scheduled emits keeps its deadline and c…
unbalancedparentheses Nov 11, 2026
1d2dcb7
fix(jobs): a shortened token deadline no longer cancels a caller-gone…
unbalancedparentheses Nov 11, 2026
1320479
fix(reply): a shortened deadline wakes the waiting caller to re-arm i…
unbalancedparentheses Nov 11, 2026
2c76646
fix(reply): contain user payload on the process-wide deadline service
unbalancedparentheses Nov 11, 2026
20642c7
fix(tasks): reserve a blocking job's worker and scope lease with its …
unbalancedparentheses Nov 11, 2026
6d1bc4b
fix(supervision): charge the restart budget once per recovery round
unbalancedparentheses Nov 11, 2026
9a00d31
style: rustfmt the review fixes
unbalancedparentheses Nov 11, 2026
f0013b6
fix(jobs): admit a job in one step with its owner's exit closing admi…
unbalancedparentheses Nov 11, 2026
f87645c
fix: one death boundary: answer every caller and fence replies, signa…
unbalancedparentheses Nov 11, 2026
1a5e431
refactor!: DeferError and Delivery are non_exhaustive; doc and model …
unbalancedparentheses Nov 11, 2026
c151f5e
docs(changelog): user-visible fixes from the 0.8 pre-release review
unbalancedparentheses Nov 11, 2026
edb3006
docs: in_actor runs after started() and cannot replace a bypass insid…
unbalancedparentheses Nov 11, 2026
3a2f57d
docs(jobs): an emit_at instant already reached is an immediate emit
unbalancedparentheses Nov 11, 2026
839e9e9
docs(jobs): make the native-job budget hard to miss; log its first re…
unbalancedparentheses Nov 11, 2026
2f66905
docs(error): keep ReplyDropped non-transient and say how to ask for a…
unbalancedparentheses Nov 11, 2026
710095d
fix(macros): #[send_handler] works when spawned-concurrency is renamed
unbalancedparentheses Nov 11, 2026
24de832
fix(examples): if-let for the deferred_replies example's defer (clippy)
unbalancedparentheses Nov 11, 2026
ca6b6d6
test(death): keep the tasks-only Park message in the tasks module (no…
unbalancedparentheses Nov 11, 2026
609f00a
test(sim): compile stack_budget out under spawned_sim
unbalancedparentheses Nov 11, 2026
773f3c7
Merge origin/main into feat/otp-exit-signals
unbalancedparentheses Nov 11, 2026
46cbef6
test: port #246's semantics_gaps and sim_ordering to the 0.8 API and …
unbalancedparentheses Nov 11, 2026
1144b87
ci: accept RUSTSEC-2026-0441 (pin-utils unmaintained) for the Loom de…
unbalancedparentheses Nov 11, 2026
e935038
test(fuzz): port the scenario harness to the 0.8 API and contracts
unbalancedparentheses Nov 11, 2026
9bf6f05
fix(verification): copy specs writable in negative_checks (nix store …
unbalancedparentheses Nov 11, 2026
a0b37e4
docs: fix intra-doc links that break the thread-only rustdoc build
unbalancedparentheses Nov 11, 2026
1dc692b
fix(examples,tests): adapt to 0.8 shutdown semantics and one-CPU nati…
unbalancedparentheses Nov 11, 2026
e11386e
fix(reply): overloaded() releases its death-fence permit before waking
unbalancedparentheses Nov 11, 2026
fb77cf8
fix: answer every caller at the death point before any wake-up or log…
unbalancedparentheses Nov 11, 2026
a62dc82
fix(jobs): the owner's exit cancels jobs without destroying their pay…
unbalancedparentheses Nov 11, 2026
28d66d5
fix(verification): restore SupervisionInd induction after merging mai…
unbalancedparentheses Nov 11, 2026
6267477
fix(tasks): a runtime drop reaches the death point before destroying …
unbalancedparentheses Nov 11, 2026
a36bff6
fix(tasks): take a blocking job's scope lease at admission, not reser…
unbalancedparentheses Nov 11, 2026
f41912b
fix(threads): an unused blocking permit returns its native slot on drop
unbalancedparentheses Nov 11, 2026
4435ae7
docs(semantics): sections 11 and 12 describe the per-round restart bu…
unbalancedparentheses Nov 11, 2026
2d5aed4
docs: state the emit_at-past-deadline and raw-receiver clock contracts
unbalancedparentheses Nov 11, 2026
4ebc3bb
verify(tla): model delayed emits and held results in DeferredJobs
unbalancedparentheses Nov 11, 2026
39c1852
style: rustfmt the unused-permit test
unbalancedparentheses Nov 11, 2026
b5f419f
test: harden the death-boundary and job regression tests (round-two r…
unbalancedparentheses Nov 11, 2026
fd25de5
Merge origin/main (#250, #255) into feat/otp-exit-signals
unbalancedparentheses Nov 11, 2026
b945a71
style(fuzz): rustfmt the ported harness handlers
unbalancedparentheses Nov 11, 2026
93f00e3
test(fuzz): an exiting actor's link is inert in the X3 oracle
unbalancedparentheses Nov 11, 2026
9684e01
test(supervision): the sibling signalled after the system deadline en…
unbalancedparentheses Nov 11, 2026
c27f37a
test(fuzz): a late link's Exit carries NoProc in the X3 oracle (0.8)
unbalancedparentheses Nov 11, 2026
1f321b9
test(fuzz): a late Shutdown notice may replace a panic reason in the …
unbalancedparentheses Nov 11, 2026
3a1fb6a
Merge remote-tracking branch 'origin/main' into fix08-local
unbalancedparentheses Nov 11, 2026
df0e8da
refactor!: one job call per shape, options first (0.8 API pass)
unbalancedparentheses Nov 11, 2026
2021e22
test(fuzz): V5 oracle bounds cohort rounds by their grants, not rebui…
unbalancedparentheses Nov 11, 2026
a98ff83
refactor!: send_request and one deadline type (0.8 API pass)
unbalancedparentheses Nov 11, 2026
6994f77
fix(reply): PendingReplies drains in insertion order (#261)
unbalancedparentheses Nov 11, 2026
f5e93e9
verification: model per-round restart charging in Cohorts (RoundCharg…
unbalancedparentheses Nov 11, 2026
763232f
verification: model job admission fence and exit cancelling held resu…
unbalancedparentheses Nov 11, 2026
49c4fe2
verification: model the death boundary in DeferredReplies (reply fenc…
unbalancedparentheses Nov 11, 2026
681b4d6
verification: split Lifecycle death into D and Discharge, model the e…
unbalancedparentheses Nov 11, 2026
98d1c38
verification: wire the new model controls, document them, refresh the…
unbalancedparentheses Nov 11, 2026
c768ccc
feat(monitor): demonitor flushes a queued Down, as OTP demonitor(Ref,…
unbalancedparentheses Nov 11, 2026
04342a1
refactor!: non_exhaustive enums and read-only structs; Down names the…
unbalancedparentheses Nov 11, 2026
21081b1
refactor!: builders for SupervisorConfig, Backoff and metrics::Config…
unbalancedparentheses Nov 11, 2026
64eca94
test(exit): run the stopped_skipped log test in its own process
unbalancedparentheses Nov 11, 2026
83d4098
test(kit): restart_child names its Duration a limit
unbalancedparentheses Nov 11, 2026
617c993
fix(bench): await fan-out ReplyReceivers through into_future
unbalancedparentheses Nov 11, 2026
7aaf93f
refactor!: seal Receiver, hide the reply module and test hooks, check…
unbalancedparentheses Nov 11, 2026
7a5a5f2
fix: clippy, rustdoc and doctest follow-ups to the sealed Receiver an…
unbalancedparentheses Nov 11, 2026
35b9fcb
refactor!: one way to get a Context or stop with Shutdown; Response n…
unbalancedparentheses Nov 11, 2026
358e8e5
ci(api): accept 0.8 as a breaking release against v0.7.3
unbalancedparentheses Nov 11, 2026
ea3a15a
test(ui): a pass case for the 0.8 request, deadline, notice and confi…
unbalancedparentheses Nov 11, 2026
93a317a
Merge origin/main (#247 planted-bug matrix, #262, #264 legacy removal…
unbalancedparentheses Nov 11, 2026
6508ac2
Merge remote-tracking branch 'origin/main' into fix08-local
unbalancedparentheses Nov 11, 2026
5f1eb5c
verification: Coordinator models the grace-expiry kill; Lifecycle, De…
unbalancedparentheses Nov 11, 2026
167c9ed
test(exit): wait for the watcher's Down after a kill (single-cpu race)
unbalancedparentheses Nov 11, 2026
256fecd
verification: trace delayed emits and held results against DeferredJo…
unbalancedparentheses Nov 11, 2026
3e8c73f
verification: trace actor-to-actor messages as Lifecycle's Send
unbalancedparentheses Nov 11, 2026
ff4e3c6
test: long-running soak harness for resource leaks (#257)
unbalancedparentheses Nov 11, 2026
7d7b33f
test(soak): port the soak harness (#257) to the 0.8 API
unbalancedparentheses Nov 11, 2026
dbd6dae
verification: the temporal ForgetSignal control also drops GraceKills…
unbalancedparentheses Nov 11, 2026
2d6c04f
Merge remote-tracking branch 'origin/main' into fix08-local
unbalancedparentheses Nov 11, 2026
0006aa3
fix(verification): the ForgetSignal liveness control also drops Grace…
unbalancedparentheses Nov 11, 2026
0058b11
verification: action coverage and floors for Lifecycle, DeferredJobs …
unbalancedparentheses Nov 11, 2026
c43c863
feat(trace): reply traces cover linked jobs, the downstream actor, sh…
unbalancedparentheses Nov 11, 2026
9677b33
Merge remote-tracking branch 'origin/main' into fix08-local
unbalancedparentheses Nov 11, 2026
83752a2
Merge remote-tracking branch 'origin/main' into fix08-local
unbalancedparentheses Nov 11, 2026
ee8c328
verification: DeferredReplies coverage floor at 14 action keys (linke…
unbalancedparentheses Nov 11, 2026
4667d63
test(semantics): a request admitted after started() panicked gets NoP…
unbalancedparentheses Nov 11, 2026
100fe99
fix(jobs): exit cancellation runs its callbacks after every caller is…
unbalancedparentheses Nov 11, 2026
c34ebf0
fix(reply): the death point's discharge retains every slot it meets u…
unbalancedparentheses Nov 11, 2026
4cbc032
fix(reply): discharge notifies parked blocking receivers before runni…
unbalancedparentheses Nov 11, 2026
04e342e
docs: MIGRATION's Receiver and metrics::Snapshot guidance matches the…
unbalancedparentheses Nov 11, 2026
ca3b3d9
docs(jobs): an async job's work is a factory run in the current turn;…
unbalancedparentheses Nov 11, 2026
6dab1bf
test: handshakes for the held-result and unused-native-permit regress…
unbalancedparentheses Nov 11, 2026
49f7833
fix(jobs): payloads dropped for an exiting owner are held until every…
unbalancedparentheses Nov 11, 2026
e65af01
fix(jobs): a keyed replacement cancels the old job in its Start step …
unbalancedparentheses Nov 11, 2026
e9ab240
fix(jobs): a cancelled or deadline-displaced held result drops outsid…
unbalancedparentheses Nov 11, 2026
4b85fb0
docs: shorten_deadline takes any Into<Deadline> (Duration, Instant or…
unbalancedparentheses Nov 11, 2026
c280235
feat(reply): InsertError::kind() and into_parts() answer every refuse…
unbalancedparentheses Nov 11, 2026
9bf44c0
Merge origin/main (#265 planted-bug follow-ups, #252 sim_test re-expo…
unbalancedparentheses Nov 12, 2026
fc7cddf
test(fuzz): main's linked-reason oracle accepts a late link's NoProc …
unbalancedparentheses Nov 12, 2026
cca5105
test: bound the in-crate unit tests that wait on actors (fail by name…
unbalancedparentheses Nov 12, 2026
3c2d68d
perf(tasks): one cancellation future per run loop, polled only on an …
unbalancedparentheses Nov 12, 2026
4ebf1f0
perf(reply): inline the slot word helpers (state, jobs, live, credits…
unbalancedparentheses Nov 12, 2026
b535c15
perf(reply): an in-turn reply publishes with one CAS, the value writt…
unbalancedparentheses Nov 12, 2026
836f6a2
perf(tasks): the run loop checks cancellation once per message, in th…
unbalancedparentheses Nov 12, 2026
befbd77
perf(reply): a turn that dropped no obligation skips the commit
unbalancedparentheses Nov 12, 2026
7f71615
bench(baseline): raw requests carry no deadline, as 0.7's request_raw
unbalancedparentheses Nov 12, 2026
c73a039
perf(reply): one slot lock for value and waker, lazily created deferr…
unbalancedparentheses Nov 12, 2026
9c1f09d
perf(tasks): the envelope lends its request slot to the turn (no per-…
unbalancedparentheses Nov 12, 2026
9a12d14
perf(tasks): a turn that finishes in its first poll needs no DeathFir…
unbalancedparentheses Nov 12, 2026
9799c4c
perf(request): the first timer of a request sleeps its timeout, witho…
unbalancedparentheses Nov 12, 2026
9f65a06
perf(reply): a request without a deadline holds no reference to its o…
unbalancedparentheses Nov 12, 2026
50d88dc
fix(request): place the first-timer fields where 9799c4c8 meant them
unbalancedparentheses Nov 12, 2026
9473fdc
perf(reply): a caller that owns an answered slot alone takes the valu…
unbalancedparentheses Nov 12, 2026
f66429c
perf(reply): the slot holds no owner; raw deadlines go to the owner's…
unbalancedparentheses Nov 12, 2026
d0aad75
Merge origin/main (#267 determinism D1-D3, #259, #268, #269) into fea…
unbalancedparentheses Nov 12, 2026
30178be
test: Sync bounds and 0.8 fixtures for the D1-D3 port onto the 0.8 ca…
unbalancedparentheses Nov 12, 2026
291f4ae
feat(sim): native jobs (pipe_blocking) run inline as one simulator st…
unbalancedparentheses Nov 12, 2026
e3878af
feat(reply)!: the caller's expiry is the only timeout authority, as O…
unbalancedparentheses Nov 12, 2026
8f07305
perf(reply): a raw deadline goes to the owner's heap only when its re…
unbalancedparentheses Nov 13, 2026
155682f
perf(tasks): a sent message carries no reply slot; a consumed Pending…
unbalancedparentheses Nov 13, 2026
e8847a3
perf(request): a clock seam for deadlines, tick arithmetic at send, a…
unbalancedparentheses Nov 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ignore = [
"RUSTSEC-2026-0194", # quick-xml via pprof/inferno (private profiling-experiments feature)
"RUSTSEC-2026-0195", # quick-xml via pprof/inferno (private profiling-experiments feature)
"RUSTSEC-2025-0057", # fxhash via monoio (private io-experiments feature)
"RUSTSEC-2026-0441", # pin-utils via loom's futures feature (Loom dev-dependency only)
]
informational_warnings = ["unmaintained", "unsound"]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: cargo test --locked --verbose
- name: Run capability examples
run: |
for example in execution_groups execution_profiles supervision_recovery supervision_tree dedicated_thread runtime_isolation byte_budget_pipeline timeout_outcomes graceful_drain protocol_timeouts streams_and_recipients registrations_and_fakes native_lifecycle coordinated_shutdown test_kit; do
for example in execution_groups execution_profiles supervision_recovery supervision_tree dedicated_thread runtime_isolation byte_budget_pipeline timeout_outcomes graceful_drain protocol_timeouts streams_and_recipients registrations_and_fakes native_lifecycle coordinated_shutdown test_kit exit_signals; do
timeout 60s cargo run --locked -p spawned-capability-examples --bin "$example"
done
timeout 60s cargo run --locked -p spawned-capability-examples --features metrics --bin framework_metrics
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Record supervisor lifecycle traces
env:
LIFECYCLE_TRACE_DIR: ${{ github.workspace }}/lifecycle-traces
run: cargo test --locked -p spawned-concurrency --features lifecycle-trace,failpoints --test supervision_trace --test cohort_trace --test tree_trace --test nested_supervision --test coverage_trace
run: cargo test --locked -p spawned-concurrency --features lifecycle-trace,failpoints --test supervision_trace --test cohort_trace --test tree_trace --test nested_supervision --test coverage_trace --test job_trace --test reply_trace --test lifecycle_trace
- name: Validate traces and negative controls
run: nix run .#check-tla -- --traces-only lifecycle-traces --negative-controls
# Which model actions, projected states and transitions the recorded
Expand Down Expand Up @@ -172,6 +172,10 @@ jobs:
env:
RUSTFLAGS: --cfg spawned_scheduler_loom
run: cargo test --locked -p spawned-concurrency --no-default-features --test mailbox_admission_loom
- name: Model-check the reply slot, job core, link table and monitor fence
env:
RUSTFLAGS: --cfg spawned_scheduler_loom
run: cargo test --locked --release -p spawned-concurrency --no-default-features --test reply_slot_loom --test registry_turn_loom --test job_core_loom --test link_fence_loom

# Data races in the runtime and its dependencies. The crates forbid
# unsafe code, so ThreadSanitizer is the sanitizer that matters here.
Expand Down
300 changes: 300 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ The return type on each protocol method determines the message kind:
- `Result<(), ActorError>` — fire-and-forget send (both modes), returns the send result
- No return / `-> ()` — fire-and-forget send (both modes), discards the send result

**Actors** implement message handlers with `#[actor]`. Each handler method is annotated with `#[request_handler]`, `#[send_handler]`, or `#[handler]` and receives a single message struct plus a `Context`. The macro generates the `Actor` trait impl and one `Handler<M>` impl per method.
**Actors** implement message handlers with `#[actor]`. Each handler method is annotated with `#[request_handler]` (answers with its return value), `#[send_handler]` (returns `()`) or `#[deferred_handler]` (answers through its `PendingReply`, now or later) and receives a single message struct plus a `Context`. The macro generates the `Actor` trait impl and one `Handler<M>` impl per method.

**Using an actor** is just calling trait methods on its `ActorRef`. Because `#[protocol]` generates `impl NameServerProtocol for ActorRef<NameServer>`, you write `ns.add(...)` and `ns.find(...)` — the macro handles message construction and mailbox dispatch behind the scenes.

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ Small, independent of the scheduler and lifecycle design, and visible to users.
|---|---|---|
| 5d | One semantics contract | A single `docs/design/semantics.md` stating every guarantee Spawned gives (message ordering and delivery, request outcomes, exit propagation through links and monitors, panic handling, restart and budget rules, shutdown order, timers, what `panic=abort` disables), each linked to the TLA+ invariant, test or example that checks it, and listing what is explicitly not guaranteed. Today these are spread across the guide, design contracts, TESTING.md and the models. New designs are reviewed against it. Erlang's semantics fit on a few pages; ours should too. |
| 5e | Exit signals as in OTP (v0.8) | Align every exit-signal rule with OTP, as one design with a TLA+ model (the planned base-lifecycle model: links, monitors, `trap_exit`) and negative controls. (1) A direct `kill` stays untrappable, but the exit it causes propagates to linked actors as a distinct `Killed` reason (OTP `killed`), which they can trap. (2) `exit(Pid, normal)` semantics: a `Normal` exit signal from another actor is ignored by an actor that is not trapping exits; other reasons, including `Shutdown`, are delivered as trappable `Exit` messages to a trapping actor, and only `Kill` cannot be trapped. Today every direct stop ignores `trap_exit`. (3) A supervisor's `Shutdown` to a trapping child runs its normal termination path, as gen_server calls `terminate` on an exit from its parent. (4) Linking to or monitoring an actor that has already exited reports `NoProc` (OTP `noproc`) instead of its earlier exit reason. Breaking for exhaustive matches on `ExitReason`: pair with `#[non_exhaustive]` (as rank 11 does for `ActorError`), and update the semantics contract, migration notes and trace mapping. |
| 5f | Restart budget counts recovery rounds (OTP alignment, v0.8) | Today every rebuilt child counts against the budget, including the siblings OneForAll and RestForOne rebuild, so those strategies exhaust a budget N times faster than OTP users expect. Count one restart per recovery round, as OTP does. Also align initial
| 5f | Restart budget counts recovery rounds (OTP alignment, v0.8) | **Done in the 0.8 stack (#240):** a OneForAll/RestForOne recovery round takes one grant when the coordinator selects it (OTP's `add_restart` once per restart action); its rebuilds take none; a failed replacement requests a new, charged round. Before, every rebuilt child counted, so those strategies exhausted a budget N times faster than OTP users expect. Remaining: `Cohorts.tla` still abstracts budgets (`Supervision.tla`'s `BudgetBounded` covers OneForOne, where a child retry is the restart action); modelling the per-round charge with a per-child negative control is follow-up work. Also align initial
startup: OTP aborts `start_link` on any child start error, while Spawned retries
a Permanent or Transient child's failed factory, panic or startup timeout during
the first start (semantics §11; found reviewing #233). Behaviour change: extend the `Supervision`/`Cohorts` TLA+ models and their Apalache inductive checks (`BudgetBounded`) to the new rule, with a negative control for counting per child, and update the Kani restart-window proofs and the guide. |
Expand Down
2 changes: 1 addition & 1 deletion concurrency/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ required-features = ["backend-experiments"]
pprof = { version = "0.15", optional = true, features = ["flamegraph"] }

[target.'cfg(spawned_scheduler_loom)'.dev-dependencies]
loom = "0.7"
loom = { version = "0.7", features = ["futures"] }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(spawned_scheduler_loom)', 'cfg(spawned_sim)', 'cfg(kani)', 'cfg(kani_slow)'] }
Expand Down
51 changes: 37 additions & 14 deletions concurrency/benches/baseline.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Process-isolated baseline driver; methodology in benchmarks/README.md.
#[path = "support/args.rs"]
mod cli;
use spawned_concurrency::{message::Message, tasks, threads, ChildHandle};
use spawned_concurrency::{message::Message, tasks, threads, ChildHandle, Deadline, ReplyReceiver};
use std::{
alloc::{GlobalAlloc, Layout, System},
future::IntoFuture,
hint::black_box,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Expand Down Expand Up @@ -98,14 +99,24 @@ fn process(work: Work) -> Ack {
struct Sink;
impl tasks::Actor for Sink {}
impl tasks::Handler<Work> for Sink {
async fn handle(&mut self, work: Work, _: &tasks::Context<Self>) -> Ack {
process(work)
async fn handle(
&mut self,
work: Work,
reply: spawned_concurrency::PendingReply<'_, Ack>,
_: &tasks::Context<Self>,
) {
reply.reply(process(work));
}
}
impl threads::Actor for Sink {}
impl threads::Handler<Work> for Sink {
fn handle(&mut self, work: Work, _: &threads::Context<Self>) -> Ack {
process(work)
fn handle(
&mut self,
work: Work,
reply: spawned_concurrency::PendingReply<'_, Ack>,
_: &threads::Context<Self>,
) {
reply.reply(process(work));
}
}
#[derive(Clone)]
Expand All @@ -115,35 +126,47 @@ enum Target {
Tokio(tokio::sync::mpsc::UnboundedSender<(Work, tokio::sync::oneshot::Sender<Ack>)>),
}
enum Reply {
Tasks(tokio::sync::oneshot::Receiver<Ack>),
Threads(mpsc::Receiver<Ack>),
Tasks(ReplyReceiver<Ack>),
Tokio(tokio::sync::oneshot::Receiver<Ack>),
Threads(ReplyReceiver<Ack>),
}
impl Target {
/// A raw request with no deadline: the 0.8 form of 0.7's `request_raw`
/// (no timeout), so the comparison with 0.7 measures the same operation.
fn request(&self, work: Work) -> Reply {
match self {
Self::Tasks(actor) => Reply::Tasks(actor.request_raw(work).expect("actor stopped")),
Self::Threads(actor) => Reply::Threads(actor.request_raw(work).expect("actor stopped")),
Self::Tasks(actor) => Reply::Tasks(
actor
.send_request(work, Deadline::Never)
.expect("actor stopped"),
),
Self::Threads(actor) => Reply::Threads(
actor
.send_request(work, Deadline::Never)
.expect("actor stopped"),
),
Self::Tokio(tx) => {
let (reply, rx) = tokio::sync::oneshot::channel();
tx.send((work, reply))
.unwrap_or_else(|_| panic!("baseline stopped"));
Reply::Tasks(rx)
Reply::Tokio(rx)
}
}
}
}
impl Reply {
fn wait(self, rt: &tokio::runtime::Handle) -> Ack {
match self {
Self::Tasks(rx) => rt.block_on(async {
Self::Tasks(rx) => rt
.block_on(rx.into_future())
.expect("reply timeout or dropped"),
Self::Tokio(rx) => rt.block_on(async {
tokio::time::timeout(Duration::from_secs(10), rx)
.await
.expect("reply timeout")
.expect("reply dropped")
}),
Self::Threads(rx) => rx
.recv_timeout(Duration::from_secs(10))
.expect("reply timeout/disconnected"),
Self::Threads(rx) => rx.recv().expect("reply timeout/disconnected"),
}
}
}
Expand Down
117 changes: 82 additions & 35 deletions concurrency/benches/designs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,41 +123,54 @@ impl Sink {
}
impl tasks::Actor for Sink {}
impl tasks::Handler<Work> for Sink {
async fn handle(&mut self, work: Work, _: &tasks::Context<Self>) -> Ack {
let start = if work.timing {
Instant::now()
} else {
work.enqueued
};
match self.work.as_str() {
"timer" => tokio::time::sleep(self.wait).await,
"blocking" => std::thread::sleep(self.wait),
"tcp" => {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
if self.socket.is_none() {
let socket = tokio::net::TcpStream::connect(self.address.unwrap())
.await
.unwrap();
socket.set_nodelay(true).unwrap();
self.socket = Some(socket);
async fn handle(
&mut self,
work: Work,
reply: spawned_concurrency::PendingReply<'_, Ack>,
_: &tasks::Context<Self>,
) {
let __reply_value: Ack = {
let start = if work.timing {
Instant::now()
} else {
work.enqueued
};
match self.work.as_str() {
"timer" => tokio::time::sleep(self.wait).await,
"blocking" => std::thread::sleep(self.wait),
"tcp" => {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
if self.socket.is_none() {
let socket = tokio::net::TcpStream::connect(self.address.unwrap())
.await
.unwrap();
socket.set_nodelay(true).unwrap();
self.socket = Some(socket);
}
let socket = self.socket.as_mut().unwrap();
let mut frame = [0u8; 64];
frame[..8].copy_from_slice(&work.id.to_le_bytes());
let expected = frame;
socket.write_all(&frame).await.unwrap();
socket.read_exact(&mut frame).await.unwrap();
assert_eq!(frame, expected);
}
let socket = self.socket.as_mut().unwrap();
let mut frame = [0u8; 64];
frame[..8].copy_from_slice(&work.id.to_le_bytes());
let expected = frame;
socket.write_all(&frame).await.unwrap();
socket.read_exact(&mut frame).await.unwrap();
assert_eq!(frame, expected);
_ => {}
}
_ => {}
}
process_at(work, start)
process_at(work, start)
};
reply.reply(__reply_value);
}
}
impl threads::Actor for Sink {}
impl threads::Handler<Work> for Sink {
fn handle(&mut self, work: Work, _: &threads::Context<Self>) -> Ack {
process(work)
fn handle(
&mut self,
work: Work,
reply: spawned_concurrency::PendingReply<'_, Ack>,
_: &threads::Context<Self>,
) {
reply.reply(process(work));
}
}
enum ReplySender {
Expand Down Expand Up @@ -188,13 +201,29 @@ impl Message for Probe {
type Result = ();
}
impl tasks::Handler<Probe> for Sink {
async fn handle(&mut self, probe: Probe, _: &tasks::Context<Self>) {
probe.reply.send(process(probe.work));
async fn handle(
&mut self,
probe: Probe,
reply: spawned_concurrency::PendingReply<'_, ()>,
_: &tasks::Context<Self>,
) {
{
probe.reply.send(process(probe.work));
}
reply.reply(());
}
}
impl threads::Handler<Probe> for Sink {
fn handle(&mut self, probe: Probe, _: &threads::Context<Self>) {
probe.reply.send(process(probe.work));
fn handle(
&mut self,
probe: Probe,
reply: spawned_concurrency::PendingReply<'_, ()>,
_: &threads::Context<Self>,
) {
{
probe.reply.send(process(probe.work));
}
reply.reply(());
}
}
enum TokioMessage {
Expand Down Expand Up @@ -264,6 +293,9 @@ enum Target {
enum Reply {
Tasks(tokio::sync::oneshot::Receiver<Ack>),
Threads(mpsc::Receiver<Ack>),
/// A framework request's receiver (its deadline is fixed at send).
FrameworkTasks(spawned_concurrency::ReplyReceiver<Ack>),
FrameworkThreads(spawned_concurrency::ReplyReceiver<Ack>),
}
impl Target {
fn request(&self, work: Work, kind: &str) -> Reply {
Expand All @@ -282,8 +314,16 @@ impl Target {
return rx;
}
match self {
Self::Tasks(actor) => Reply::Tasks(actor.request_raw(work).expect("actor stopped")),
Self::Threads(actor) => Reply::Threads(actor.request_raw(work).expect("actor stopped")),
Self::Tasks(actor) => Reply::FrameworkTasks(
actor
.send_request(work, Duration::from_secs(10))
.expect("actor stopped"),
),
Self::Threads(actor) => Reply::FrameworkThreads(
actor
.send_request(work, Duration::from_secs(10))
.expect("actor stopped"),
),
Self::Tokio(tx) => {
let (reply, rx) = tokio::sync::oneshot::channel();
tx.send(TokioMessage::Request(work, reply))
Expand All @@ -300,6 +340,11 @@ impl Reply {
return match self {
Self::Tasks(rx) => futures::executor::block_on(rx).expect("reply dropped"),
Self::Threads(rx) => rx.recv().expect("reply dropped"),
Self::FrameworkTasks(rx) => {
futures::executor::block_on(std::future::IntoFuture::into_future(rx))
.expect("reply failed")
}
Self::FrameworkThreads(rx) => rx.recv().expect("reply failed"),
};
}
match self {
Expand All @@ -312,6 +357,8 @@ impl Reply {
Self::Threads(rx) => rx
.recv_timeout(Duration::from_secs(10))
.expect("reply timeout/disconnected"),
Self::FrameworkTasks(rx) => rt.block_on(async { rx.await.expect("reply failed") }),
Self::FrameworkThreads(rx) => rx.recv().expect("reply failed"),
}
}
}
Expand Down
Loading
Loading