Files
smarm/Cargo.toml
T
Claude (sandbox)andClaude (sandbox) ca1c98336e feat(scheduler,runtime): non-panicking try_spawn for at-capacity load shedding
allocate_slot() panics on a full slab; for a load-shedding caller (an
accept loop spawning one actor per connection) that panic lands in the
spawning actor, which then crash-loops under Restart::Transient into the
still-full slab until its restart budget is spent — and the service stops
accepting entirely. Observed live (urus slowloris scaling, 2026-08-10).
A full slab is a routine overload condition for such callers, not an
invariant violation.

- RuntimeInner::try_allocate_slot() -> Option<u32>: the non-panicking
  core; a single pop under the free-list lock, so the claim is atomic
  (claim-or-report — no check-then-spawn TOCTOU, no headroom margin).
  allocate_slot() is now a thin panicking wrapper over it.
- scheduler::try_spawn / try_spawn_under_with -> Result<JoinHandle,
  SpawnError>: parity with spawn/spawn_under_with except a full slab
  returns Err(SpawnError::AtCapacity) instead of panicking. Minimal
  surface per the agreed strategy; the remaining _with/_addr mirrors are
  trivial wrappers if ever needed.
- Slot-first ordering on the try path (reverse of spawn's stack-first):
  under overload Err is the hot path, and a rejection costs one mutex
  pop — no mmap/pool-pop + init + recycle per shed unit of work. A
  drop-guard returns the claimed slot if stack allocation panics in the
  claim-to-install window (would otherwise leak and trip run()'s
  teardown slot-leak debug_assert).
- SpawnError: non_exhaustive, Display + std::error::Error.
- spawn and every existing call site untouched: the panic remains the
  correct loud invariant check at internal/bounded spawn sites.

tests/try_spawn.rs: parity when slots free; exact slab accounting at
capacity (Err, no panic, repeatable); custom-shape try refuses before
stack allocation; self-heal after slots free; plain spawn still panics
(surfaced via JoinError payload); 4-thread race for the last slots
claims exactly the free count; SpawnError impl checks.

Design doc: smarm-suggestion-try-spawn.md. Downstream consumer change
(canned 503 on AtCapacity in urus's accept loop) is urus scope, not
smarm.

(cherry picked from commit 36de4b36aeaa72b2a5f9f3797b9854652656dcf6)
2026-08-13 15:03:16 +02:00

111 lines
2.7 KiB
TOML
Raw Permalink 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.
[package]
name = "smarm"
version = "0.6.1"
edition = "2021"
rust-version = "1.95"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(loom)"] }
[lints.clippy]
# Library code must never hide a panic behind unwrap/expect. Both are denied; an
# intentional panic is written explicitly as `match { Err(e) => panic!(..) }`.
# panic!/unreachable! are deliberately left un-linted as the blessed explicit
# form. Enforced on the library target only (`cargo clippy --lib`); tests and
# examples unwrap freely and are not gated.
unwrap_used = "deny"
expect_used = "deny"
[features]
default = ["rq-mutex"]
smarm-trace = []
# RFC 007: native causal profiling. Zero cost when off (cf. smarm-trace): the
# hook in `maybe_preempt` and the resume-path fast-forward compile away; the
# two Slot ledger fields exist regardless and stay 0 (budget_cycles precedent).
smarm-causal = []
# RFC 016 Chunk 2: cycle-accurate per-actor time-budget accounting. Off by
# default — it costs two extra RDTSC reads per actor resume on the hot path
# (D6). The `ActorInfo.budget_cycles` field exists regardless; it just stays 0
# unless this is enabled.
budget-accounting = []
# RFC 016 Chunk 4: the live observer gen_server (src/observer.rs). Off by
# default (DECISION D10) — the read primitive (Chunks 1–3) is always present
# and unflagged; only the optional gen_server transport sits behind this, so a
# release build pays nothing for an observer it never starts.
observer = []
# Run-queue selection: exactly one, compile-time (see src/run_queue.rs).
# Non-default variants need --no-default-features (features are additive).
rq-mutex = []
rq-mpmc = []
rq-striped = []
[build-dependencies]
cc = "1"
[dependencies]
libc = "0.2"
[target.'cfg(loom)'.dependencies]
loom = "0.7"
[dev-dependencies]
libc = "0.2"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "sync", "time"] }
[profile.dev]
panic = "unwind"
[profile.release]
panic = "unwind"
lto = "thin"
codegen-units = 1
[[bench]]
name = "primes"
harness = false
[[bench]]
name = "multi_scheduler"
harness = false
[[bench]]
name = "general"
harness = false
[[bench]]
name = "smarm_favored"
harness = false
[[bench]]
name = "tokio_favored"
harness = false
[[bench]]
name = "rq_micro"
harness = false
[[bench]]
name = "rq_runtime"
harness = false
[[bench]]
name = "switch_cost"
harness = false
# RFC 016 Chunk 4 — the live observer dump. Needs the optional gen_server.
[[example]]
name = "observer"
required-features = ["observer"]
[[example]]
name = "causal_pipeline"
required-features = ["smarm-causal"]
[[example]]
name = "causal_attrib_probe"
required-features = ["smarm-causal"]
[[example]]
name = "causal_probe"
required-features = ["smarm-causal"]