swactor/CLAUDE/notes/progress.md
Developer cf616199b2 feat: shutdown unpark + bug-inspired tests
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>
2026-02-12 11:34:58 +00:00

90 lines
5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Progress Log
## Current Stage: Phase 1 — Research + First Improvement Cycle
### Status: Cycle 4 COMPLETE
## Plan Overview
1. **Phase 0**: Codebase audit — understand current swactor architecture, existing tests, benchmarks ✅
2. **Phase 1**: Broad survey + interleaved improvements
3. **Phase 2**: Deeper improvements based on findings
4. **Phase 3**: Testing methodology improvements
5. **Phase 4**: Final evaluation & documentation
## Completed This Session
### Cycle 1: Fairness (Message Budget)
- **Research**: Studied ractor, tokio, Erlang/OTP BEAM, Linux CFS/EEVDF, libuv
- **Finding**: `tick_all` drained ENTIRE mailbox per actor per tick — critical fairness bug
- BEAM uses 4000 reduction budget, tokio uses 128-op cooperative budget
- Swactor had zero budget — one hot actor could starve all others on same worker
- **Implementation**: Added `actor_message_budget` to `RuntimeConfig` (default: 64)
- Modified `tick_all` to break after `budget` messages per actor
- `budget=0` means unlimited (backward compatible)
- **Tests**: 3 new fairness tests (hot_actor_does_not_starve_cold_actor, unlimited_budget_drains_all, budget_messages_drain_across_multiple_ticks)
- **Benchmarks**: Added fairness benchmark group (cold_latency_under_pressure, throughput_by_budget)
- **Fixes**: Updated RuntimeConfig struct literals across crates (python, runtime-dashboard, mt_benchmarks)
- **Result**: 45 tests pass (42 original + 3 new), all workspace crates compile
### Cycle 2: Stress Tests, Benchmarks, Research Expansion
- **Research**: Added Kameo and Actix analysis to synthesis
- Actix uses custom Vyukov lock-free MPSC queue (why it's fastest)
- Kameo has dual bounded/unbounded mailbox, default capacity 64
- Both use vtable dispatch (not Box<dyn Any> downcast)
- Actix has 256-message assertion guard (validates our budget approach)
- **Stress tests**: 6 new tests
- `message_ordering_preserved_under_budget` — FIFO order with budget=8
- `mt_stress_many_senders_one_receiver` — 50 senders × 100 msgs, 4 threads
- `mt_stress_concurrent_spawn_and_send` — 200 concurrent spawn+send, 4 threads
- `mt_chain_spawning_under_load` — 50-level chain across 2 workers
- `mt_panic_isolation_under_load` — 10 panicking + 10 healthy actors, 4 threads
- `sustained_throughput_does_not_drop_messages` — 10 batches × 100 msgs
- **Benchmarks**: 2 new benchmark groups
- `msg_size`: throughput and send_latency by message size (8B-4KB)
- `contention`: fanin (1-100 senders to 1 sink), cross_worker (1-4 threads)
- **Result**: 51 tests pass (42 original + 3 fairness + 6 stress), all workspace compiles
### Cycle 3: Thread Parking (Adaptive Backoff)
- **Implementation**: Replaced `thread::sleep` with `thread::park_timeout` in worker run loop
- Workers register `thread::current()` via `OnceLock<Thread>` on startup
- `send_to` and `spawn` call `Thread::unpark()` on target worker
- Cross-worker sends from `WorkerContext` also unpark target
- Zero new dependencies (uses `std::sync::OnceLock` + `std::thread::park_timeout`)
- **Design source**: Tokio's parker state machine, Linux NO_HZ adaptive ticks
- **Benefits**: Parked workers wake instantly when work arrives (vs waiting for sleep timer)
- Reduces idle-to-active latency from up to 1ms to near-zero
- No overhead on hot path — `unpark()` is no-op if thread isn't parked
- **Tests**: 1 new test (`mt_parked_worker_wakes_on_send`)
- **Result**: 52 tests pass (51 + 1 new), all workspace compiles
### Cycle 4: Shutdown Fix + Bug-Inspired Tests
- **Shutdown improvement**: `shutdown()` now unparks all workers for immediate exit
- Previously, parked workers wouldn't notice shutdown until park_timeout expired
- **Bug-inspired tests** (5 new, from competitor bug reports):
- `stats_snapshot_is_read_only` — from ractor #310 (destructive get_children)
- `stats_under_load_do_not_interfere_with_processing` — stats don't affect msg processing
- `shutdown_wakes_parked_workers_immediately` — validates fast shutdown with parking
- `mt_send_after_run_delivers_to_running_actors` — from kameo #185 (startup delivery)
- `budget_respected_even_with_self_sends` — from actix #515 (mailbox bypass)
- **Result**: 57 tests pass, all workspace compiles
### Research Notes
- Full analysis in `CLAUDE/notes/research_synthesis.md`
- Baseline benchmarks in `CLAUDE/notes/baseline_benchmarks.md`
- Constraints in `CLAUDE/notes/constraints.md`
## Next Steps
- [x] **Cycle 2: Stress testing + property-based tests** ✅
- [x] **Cycle 3: Adaptive backoff with thread parking** ✅
- [x] **Cycle 4: Enhanced benchmarks + bug-inspired tests** ✅
- [ ] **Cycle 5: Work stealing exploration**
- Evaluate feasibility of actor migration between workers
- BEAM two-tier approach: reactive steal + periodic migration
## Open Questions
- Should budget be configurable per-actor (not just per-runtime)?
- Is 64 the right default budget? Benchmarks show budget=32 slightly faster for throughput
- Thread parking: how to handle the notification mechanism without adding deps?
## Blockers
- (none)