(WIP) agent-fuzz-harness #31

Merged
zacheryasc merged 23 commits from cfuzz into master 2026-02-13 07:11:25 +00:00
Owner

Sent the agent out to review public histories of various similar architectures and implement improvements. Needs review and cleaning.

Sent the agent out to review public histories of various similar architectures and implement improvements. Needs review and cleaning.
zacheryasc added 20 commits 2026-02-12 14:29:44 +00:00
Research across ractor, tokio, Erlang/OTP BEAM, Linux CFS, and libuv
revealed that tick_all drained the entire mailbox per actor per tick,
allowing one hot actor to starve all others on the same worker.

- Add `actor_message_budget` to RuntimeConfig (default: 64 msgs/actor/tick)
- Modify tick_all to break after budget messages, yielding to next actor
- budget=0 restores unlimited (backward compatible) behavior
- 3 new fairness tests validating hot-cold actor scenarios
- New fairness benchmark group (cold_latency_under_pressure, throughput_by_budget)
- Fix RuntimeConfig struct literals across workspace crates

Inspired by BEAM's 4000-reduction budget and tokio's 128-op cooperative budget.
All 45 tests pass (42 original + 3 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cycle 2 of the competitor analysis improvement loop.

Research additions:
- Kameo: async on tokio, dual bounded/unbounded mailbox (default 64),
  Erlang-style supervision links, vtable dispatch
- Actix: custom Vyukov lock-free MPSC queue (why it's fastest),
  256-message assertion guard (validates our budget), Context-as-Future

New stress tests (6):
- Message ordering preserved under small budget (budget=8)
- Multi-threaded: 50 senders × 100 msgs to one receiver (4 threads)
- Concurrent spawn+send of 200 actors (4 threads)
- 50-level chain spawning across 2 workers
- Panic isolation: 10 panicking + 10 healthy actors (4 threads)
- Sustained throughput: 10 batches of 100 msgs with interleaved ticks

New benchmark groups:
- msg_size: throughput and send_latency by message size (8B-4KB)
- contention: fanin (1-100 senders), cross_worker (1-4 threads)

All 57 tests pass (51 runtime_api + 5 transport + 1 doctest).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace thread::sleep with thread::park_timeout in worker backoff loop.
Workers register their thread handle via OnceLock<Thread> on startup.
When send_to or spawn routes work to a worker, Thread::unpark() wakes
it instantly instead of waiting for the sleep timer to expire.

Inspired by tokio's parker state machine and Linux NO_HZ adaptive ticks.

Implementation:
- Runtime stores Vec<OnceLock<Thread>> for worker thread handles
- Workers call OnceLock::set(thread::current()) on startup
- Runtime::send_any, spawn_any, and WorkerContext cross-worker sends
  call notify_worker() → Thread::unpark() on the target worker
- TickContext carries worker_threads reference for cross-worker notification
- Zero new dependencies (std::sync::OnceLock + std:🧵:park_timeout)

Benefits:
- Parked workers wake instantly when work arrives (vs up to 1ms sleep delay)
- No overhead on hot path — unpark() is no-op if thread isn't parked
- Single-threaded tick() mode unaffected (OnceLock never set)

All 58 tests pass (52 runtime_api + 5 transport + 1 doctest).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
shutdown() now wakes all parked workers immediately for fast exit.
5 new tests from competitor bug patterns: ractor #310 (destructive
snapshots), kameo #185 (startup delivery), actix #515 (mailbox bypass),
plus stats-under-load and shutdown-wakes-parked validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace blind round-robin placement with load-aware strategy that reads
per-worker stats (actor count + mailbox depth) and biases toward lighter
workers. Falls back to round-robin when stats are equal (initial burst).

Deep research on work stealing across Tokio (steal-half, LIFO slot),
Go (M:N scheduler), BEAM (proactive migration), and ForkJoinPool.
Full actor migration is feasible but deferred due to message-loss window.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add optional bounded mailboxes to prevent unbounded memory growth.
MailboxOverflow enum: DropNewest (discard incoming) or DropOldest
(evict oldest to make room). Default capacity=0 preserves unbounded
behavior for full backward compatibility.

messages_dropped counter added to WorkerStats for observability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add spawn_restartable(actor, factory, max_restarts) to Runtime and Ctx.
On panic, the actor is recreated using the factory with fresh state and
cleared mailbox. After max_restarts exhausted, permanently poisoned.

AnyActor::try_restart() trait method with backward-compatible default.
Factory stored as Arc<dyn Fn() -> A + Send + Sync> — safe to read after
catch_unwind since factory fields are never touched by handle_any.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Poisoned actors are now removed from ActorPool and AddressMap after
tick_all (phase 7). Previously they leaked indefinitely. Sends to
cleaned-up actors return Err instead of silently discarding — callers
learn the actor is gone.

Known bug class in Akka (#22990), C++ Actor Framework (#420).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add actor lifecycle hooks and two-mode graceful stop mechanism:

- ActorInterface::on_start() called once before first message (panic = poison)
- ActorInterface::on_stop() called on graceful stop (NOT on panic - unsafe)
- ctx.stop_self() for immediate self-stop after current message
- runtime.stop_actor() for external stop (PoisonPill semantics, queued)
- Separate stats tracking: stops counter distinct from panics
- 12 new behavioral tests, 82 total passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds deterministic tick-based timer scheduling to the actor runtime.
Timers are per-worker (no cross-thread sync) and tick-counted (not
wall-clock), making them suitable for simulation and testing.

API: ctx.send_after_ticks() for one-shot, ctx.send_interval_ticks()
for repeating timers. Timer messages route through the full delivery
system (local pool, cross-worker transfer, and external inboxes).

Fixes over-aggressive interval timer GC that incorrectly removed
timers targeting inboxes and cross-worker actors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add proptest-state-machine for stateful property testing of the runtime,
covering FIFO ordering, budget fairness, timer correctness, mailbox bounds,
and spawn tracking. Extend cargo-fuzz with stop, restart, and timer actions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add NameRegistry (String → ActorAddress) to delivery.rs with forward and
reverse maps for O(1) lookup and cleanup. Actors can be spawned with names
via rt.spawn_named() / ctx.spawn_named(), looked up via where_is(), and
names are automatically freed when actors stop or panic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add MonitorRegistry for death watch subscriptions. Actors subscribe via
ctx.monitor(target) and receive a Down { addr, reason } message when the
target dies (stop or panic). Supports stacking, demonitor, and automatic
cleanup of dead watcher subscriptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add GroupRegistry for named actor groups. Actors join/leave groups via
rt.join_group() / ctx.join_group(), and messages can be broadcast to all
members via rt.publish_to() / ctx.publish(). Groups auto-create on first
join, auto-delete when empty, and members are auto-removed on death.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Runtime::ask() and Ask<R> wrapper for convenient request-response.
Creates a temporary inbox, sends the request (with reply address via
closure), and provides recv_ticking() for automatic tick-until-response.
Purely sugar over the existing inbox pattern — no implicit auto-reply.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New benchmark group measuring named spawn/lookup (~2.4µs), group publish
(linear O(N)), monitor setup, and ask roundtrip (~4.5µs). All registry
operations efficient with minimal overhead vs baseline operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reflect current state of tick_once (8 phases), TickContext (3 new
registries + stats_hook + worker_threads), TimerWheel, lifecycle
hooks, cleanup_dead with StopReason/Down notifications/registry
cleanup, and expanded Ctx/Runtime public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add three features to enable Erlang-style supervision:

1. ActorInterface::handle_down() — callback for monitored actor deaths,
   allowing actors to react to Down messages without making Down their
   Incoming type. Backward-compatible: actors with Incoming=Down still
   receive through handle().

2. ctx.stop_actor(addr) — send graceful stop to another actor from
   handler context using PoisonPill semantics.

3. Supervisor actor — manages child actors with configurable restart
   policies (Permanent/Transient/Temporary), OneForOne strategy, and
   meltdown detection (max_restarts). Built entirely on existing
   primitives (monitor, spawn, Down, lifecycle hooks).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add coordinated restart strategies to the Supervisor actor. OneForAll
restarts all children when one fails; RestForOne restarts the failed
child and all children after it in spec order. Uses a SupervisorPhase
state machine to coordinate stop signals and Down confirmations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Router<M> actor that manages a pool of identical workers and
distributes incoming messages via configurable routing strategies:
RoundRobin, Random, and Broadcast. Workers are monitored and
auto-replaced on failure with meltdown protection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zacheryasc added 1 commit 2026-02-12 16:37:59 +00:00
One page per cycle covering motivation, competitor analysis, implementation,
design decisions, tests added, and results. Plus an overview/index page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zacheryasc added 1 commit 2026-02-12 18:06:15 +00:00
Replace SipHash with identity hasher on all hot-path HashMaps keyed by
ActorAddress. Since addresses are crypto-random, the first 8 bytes serve
as an excellent hash directly. Microbenchmarks show 1.9-4.6x lookup
speedup depending on map size.

- Custom Hash impl for ActorAddress (8-byte write_u64 instead of 32)
- AddrHasher/AddrBuildHasher identity hasher in delivery.rs
- AddrMap<V>/AddrSet type aliases used in 7 HashMap sites
- Stop-requests is_empty() short-circuit in tick_all inner loop
- 3 new behavioral tests, component microbenchmarks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zacheryasc added 1 commit 2026-02-13 07:06:07 +00:00
Debloat the core swactor crate by moving higher-level features to a new
swactor-std crate, leaving core with only the essential actor primitives
(spawn, send, stop, timers, lifecycle hooks).

Phase 1: Move Supervisor + Router to swactor-std
- Supervisor (ChildSpec, RestartPolicy, SupervisorStrategy) and Router
  (RoutingStrategy) extracted to crates/std/
- ~465 lines removed from src/actor.rs

Phase 2: Remove restart fields from Actor<A>
- Actor<A> slimmed to { inner: A } — no restart_factory, max_restarts
- Removed spawn_restartable from Ctx and Runtime
- Simplified panic handler: always poison, never inline restart
- Supervision-based restart via Supervisor in swactor-std

Phase 3: RuntimeExtension trait + registry extraction
- New src/extension.rs: RuntimeExtension trait (on_actor_death,
  cleanup_dead, as_any) — single hybrid hook for lifecycle events
- ContextInner slimmed from 11 methods to 5 (removed 7 registry
  methods, added extension())
- Ctx slimmed: removed all registry methods, added extension() accessor
- Runtime: removed 3 registry fields + 9 methods, added
  with_extension() builder + extension() accessor
- TickContext: replaced 3 registry fields with extension hook
- tick_once phase 7: uses ext.on_actor_death() + ext.cleanup_dead()
- Moved NameRegistry, MonitorRegistry, GroupRegistry from delivery.rs
  to swactor-std
- StdExtension wraps the 3 registries, implements RuntimeExtension
- Extension traits: CtxMonitoring, CtxNaming, CtxGroups on Ctx;
  RuntimeNaming, RuntimeGroups on Runtime
- Down, StopReason, MonitorRef, handle_down remain in core
- AddrMap/AddrSet/AddrBuildHasher made public, re-exported
- MonitorRef::from_raw(u64) added for cross-crate construction

All 140 tests pass. Benchmarks updated.

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
zacheryasc force-pushed cfuzz from 812826bb0a to 8a9ff9aa5a 2026-02-13 07:10:00 +00:00 Compare
zacheryasc merged commit f4f26c9f41 into master 2026-02-13 07:11:25 +00:00
zacheryasc deleted branch cfuzz 2026-02-13 07:11:26 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: zacheryasc/swactor#31
No description provided.