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>
2.9 KiB
Cycle 1: Per-Actor Message Budget for Tick Fairness — Development History
Commit:
ef87f7e· 8 files · Priority: P0 (critical bug fix)
Motivation
The tick_all function in worker.rs drained the entire mailbox for each actor before moving to the next:
while let Some(msg) = slot.mailbox.pop_front() {
// processes ALL messages for actor A before moving to actor B
}
If actor A had 10,000 queued messages, all other actors on the same worker were completely starved until A finished. This is a critical fairness bug — every other runtime studied prevents this.
Competitor Analysis
| Runtime | Fairness Mechanism | Budget |
|---|---|---|
| Erlang/OTP BEAM | Reduction counting, preemptive | 4,000 reductions |
| Tokio | Cooperative budgeting | 128–256 operations |
| libuv/Node.js | Round-robin across handlers | No single handler drains completely |
| Linux CFS | vruntime-based fairness | Time slices enforced |
| Ractor | N/A (1 task = 1 actor via tokio) | Inherited from tokio |
| Swactor (before) | None | Unlimited drain |
The BEAM's reduction budget (4,000 per process before preemption) is the gold standard for actor fairness. Tokio's cooperative budget (128 ops) serves a similar purpose for async tasks. Actix has a 256-message assertion guard that validates the approach.
Implementation
- Added
actor_message_budget: usizetoRuntimeConfig(default: 64) - Modified
tick_allinworker.rsto break afterbudgetmessages per actor per tick budget=0means unlimited (100% backward compatible)- Updated
RuntimeConfigstruct literals across all crates (python, runtime-dashboard, mt_benchmarks)
Key files modified: src/worker.rs, src/config.rs, benches/runtime_benchmarks.rs, tests/runtime_api.rs
Design Decisions
- Budget of 64 chosen as default — between BEAM's 4,000 (too generous for swactor's coarser granularity) and tokio's 128 (per-op vs per-message). Benchmarks showed budget=32 was slightly faster for throughput, but 64 provides more fairness headroom.
- Per-runtime, not per-actor — simpler configuration, matching the BEAM model where the reduction budget is global. Per-actor budgets could be added later as an extension.
- budget=0 means unlimited — backward compatibility for users who want the old behavior.
Tests Added
3 new behavioral tests (42 → 45 total):
hot_actor_does_not_starve_cold_actor— hot actor with many messages doesn't prevent cold actor from processingunlimited_budget_drains_all— budget=0 preserves old behaviorbudget_messages_drain_across_multiple_ticks— excess messages carry over to next tick
Benchmarks added: fairness/cold_latency_under_pressure, fairness/throughput_by_budget
Result
- 45 tests pass (42 original + 3 new)
- All workspace crates compile
- Baseline benchmarks established for future comparison