(2) sync facade + shuttle/loom backends + workspace sweep#1542
Draft
daniel-noland wants to merge 1 commit into
Conversation
5 tasks
d560e77 to
aa509ce
Compare
03fec49 to
8878b3c
Compare
aa509ce to
cdf3507
Compare
9632629 to
2be0637
Compare
This was referenced May 15, 2026
3 tasks
1932e47 to
1ce8d26
Compare
8 tasks
c87ff7f to
e292f12
Compare
1ce8d26 to
8d86891
Compare
Introduce a `parking_lot`-shaped facade over `Mutex` / `RwLock`, ship
the four backends together (std / parking_lot / shuttle / loom), and
rewire the data-path crates through it in a single atomic step.
The four pieces are bundled because a partial sweep leaves the
workspace in a state where the facade and its consumers disagree on
whether `Mutex::lock` returns `LockResult<Guard>` or a naked guard.
The shuttle/loom wrappers strip `LockResult` in exactly the same
poison-as-panic shape as the std wrapper, so splitting them re-
introduces that disagreement under the test/shuttle and test/loom CI
jobs.
## std::sync wrapper (poison-as-panic)
New `concurrency::sync` module exposing `Mutex` / `RwLock` with a
`parking_lot`-shaped naked-guard surface backed by a poison-as-panic
wrapper around `std::sync`. Workspace policy treats a crashed thread
as a crashed process: poison surfacing inside a lock acquire panics
through `concurrency::sync::poisoned()` rather than handing back a
possibly-torn `LockResult`.
* One cold `#[inline(never)] fn poisoned() -> !` keeps the hot path
branch-free; the wrapper is a single `match` per acquire.
* `RwLock::upgradable_read` is implemented as exclusive `write()`
for now -- sound but lossy. Documented at the backend module
level.
* `Arc`, `Weak`, atomics, `Condvar`, `mpsc`, `OnceLock`, etc. are
re-exported from `std::sync` unchanged.
## parking_lot as the production default
Adds a `parking_lot` Cargo feature (enabled by default) and
`concurrency/src/sync/parking_lot_backend.rs` -- a zero-cost
re-export of `parking_lot::{Mutex, RwLock}` plus the std re-exports
for everything `parking_lot` doesn't ship.
* Backend routing in `concurrency/src/sync/mod.rs` picks
`parking_lot_backend` when `feature = "parking_lot"` is on, falls
back to `std_backend` otherwise.
* `_strict_provenance` feature overrides `parking_lot` and routes
through `std_backend`: `parking_lot_core::word_lock` uses
integer-to-pointer casts that `-Zmiri-strict-provenance` rejects,
and the CI miri job (`--features=_strict_provenance`) exercises
the fallback slot, which needs `std::sync` underneath.
* Workspace `Cargo.toml` gets a `tokio` parking_lot override entry
so the `tokio` parking_lot feature is enabled workspace-wide.
## shuttle backend
`concurrency/src/sync/shuttle_backend.rs` mirrors `std_backend.rs`:
a thin poison-as-panic wrap around `shuttle::sync::{Mutex, RwLock}`
returning naked guards, plus re-exports for `Arc` / `Weak` / atomics
/ `Condvar` / `mpsc` that pass through unchanged. `OnceLock` falls
back to `std::sync` because shuttle does not ship one (it is
pure-std machinery and does not need model-checker integration).
Three feature flags share one `dep:shuttle`, arranged as a chain
(`shuttle_dfs` -> `shuttle_pct` -> `shuttle`):
* `shuttle` -- random scheduler (default first choice).
* `shuttle_pct` -- PCT, biases toward rare interleavings.
* `shuttle_dfs` -- DFS, exhaustive small-state exploration.
Same backend, scheduler chosen at runtime (by `concurrency::stress`,
added in the follow-on PR). The chain means a single `feature =
"shuttle"` cfg check is true under every variant, so the routing in
`sync/mod.rs` and the macro gates collapse to single-feature checks
(no `any(shuttle, shuttle_pct, shuttle_dfs)` chains).
## loom backend
`concurrency/src/sync/loom_backend.rs` mirrors the std and shuttle
wrappers: poison-as-panic around `loom::sync::{Mutex, RwLock}`
returning naked guards, with `LockResult` / `TryLockError` /
`OnceLock` etc. re-exported from `std::sync` because loom does not
ship them.
Loom 0.7 has no `Weak<T>` and no `Arc::downgrade`. The backend ships
a local `Arc<T>` wrapper that adds `downgrade` plus a `Weak<T>` shim
that holds a *strong* clone of the inner `loom::sync::Arc` until the
`Weak` itself drops. Documented quirks (`Weak::upgrade` always
succeeds after a `downgrade`, `Arc::weak_count` panics rather than
silently returning 0, last `Arc` drop does not free when a `Weak`
is live) are explicit at the module level -- they bound what loom
can model, and silent fallbacks would let assertions pass for the
wrong reason on every backend.
`sync/mod.rs` routes the loom feature ahead of every other backend.
## Workspace transition
Sweep the data-path crates (`flow-entry`, `flow-filter`, `nat`,
`net`, `pipeline`, `routing`, `stats`) to consume
`dataplane_concurrency::sync::{Arc, Mutex, RwLock, atomic, ...}`
instead of `std::sync::*` / `parking_lot::*` directly.
* Call sites drop the `.unwrap()` / `.expect("poisoned")` noise the
`LockResult`-shaped surface forced, since the facade returns
naked guards. Same shape as the existing `parking_lot::Mutex`
consumers.
* `concurrency_mode(std)` annotations at QSBR-touching test sites
in `nat::stateful::apalloc::test_alloc`,
`routing::fib::test`, and `flow-entry::flow_table::table::tests`
keep the existing std-only feature gating intact.
* `nat::portfw::flow_state::get_packet_port_fw_state`
simplification: the explicit `drop(guard)` dance before `debug!`
calls is removed (per the recorded author intent that it isn't
worth the code complexity); the guard now drops implicitly at
the end of scope.
Mechanical-by-design -- the sweep is mostly `use` rewrites and
`.unwrap()` removal. The semantic change is one step: every
consumer now gets the facade-routed primitives, so flipping the
loom/shuttle features at the top of the workspace is now
meaningful. The follow-on PR adds `#[concurrency::test]`, the
`stress` dispatcher, and the `thread::scope` shim that let a
single test source file run under any backend.
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
8d86891 to
0364eb1
Compare
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduce a
parking_lot-shaped facade overMutex/RwLock, ship the four backends together (std / parking_lot / shuttle / loom), and rewire the data-path crates through it in a single atomic step.The four pieces are bundled because a partial sweep leaves the workspace in a state where the facade and its consumers disagree on whether
Mutex::lockreturnsLockResult<Guard>or a naked guard. The shuttle/loom wrappers stripLockResultin exactly the same poison-as-panic shape as the std wrapper, so splitting them re-introduces that disagreement under thetest/shuttleandtest/loomCI jobs.std::sync wrapper (poison-as-panic)
concurrency::syncmodule exposingMutex/RwLockwith aparking_lot-shaped naked-guard surface backed by a poison-as-panic wrapper aroundstd::sync. Workspace policy treats a crashed thread as a crashed process: poison surfacing inside a lock acquire panics throughconcurrency::sync::poisoned()rather than handing back a possibly-tornLockResult.#[inline(never)] fn poisoned() -> !keeps the hot path branch-free; the wrapper is a singlematchper acquire.RwLock::upgradable_readis implemented as exclusivewrite()for now -- sound but lossy. Documented at the backend module level.Arc,Weak, atomics,Condvar,mpsc,OnceLock, etc. are re-exported fromstd::syncunchanged.parking_lot as the production default
parking_lotCargo feature (enabled by default) andconcurrency/src/sync/parking_lot_backend.rs-- a zero-cost re-export ofparking_lot::{Mutex, RwLock}plus the std re-exports for everythingparking_lotdoesn't ship.concurrency/src/sync/mod.rspicksparking_lot_backendwhenfeature = "parking_lot"is on, falls back tostd_backendotherwise._strict_provenancefeature overridesparking_lotand routes throughstd_backend:parking_lot_core::word_lockuses integer-to-pointer casts that-Zmiri-strict-provenancerejects, and the CI miri job (--features=_strict_provenance) exercises the fallback slot, which needsstd::syncunderneath.Cargo.tomlgets atokioparking_lot override entry so thetokioparking_lot feature is enabled workspace-wide.shuttle backend
concurrency/src/sync/shuttle_backend.rsmirrorsstd_backend.rs: a thin poison-as-panic wrap aroundshuttle::sync::{Mutex, RwLock}returning naked guards, plus re-exports forArc/Weak/ atomics /Condvar/mpscthat pass through unchanged.OnceLockfalls back tostd::syncbecause shuttle doesn't ship one.dep:shuttle, arranged as a chain (shuttle_dfs->shuttle_pct->shuttle):shuttle-- random scheduler (default first choice).shuttle_pct-- PCT, biases toward rare interleavings.shuttle_dfs-- DFS, exhaustive small-state exploration.Same backend, scheduler chosen at runtime (by
concurrency::stress, added in the follow-on PR). The chain means a singlefeature = "shuttle"cfg check is true under every variant, so the routing insync/mod.rscollapses to single-feature checks (noany(shuttle, shuttle_pct, shuttle_dfs)chains).loom backend
concurrency/src/sync/loom_backend.rsmirrors the std and shuttle wrappers: poison-as-panic aroundloom::sync::{Mutex, RwLock}returning naked guards, withLockResult/TryLockError/OnceLocketc. re-exported fromstd::syncbecause loom doesn't ship them.Weak<T>and noArc::downgrade. The backend adds a localArc<T>wrapper that addsdowngradeplus aWeak<T>shim that holds a strong clone of the innerloom::sync::Arcuntil theWeakitself drops. Documented quirks (Weak::upgradealways succeeds after adowngrade,Arc::weak_countpanics rather than silently returning 0, lastArcdrop does not free when aWeakis live) are explicit at the module level -- silent fallbacks would let assertions pass for the wrong reason on every backend.sync/mod.rsroutes the loom feature ahead of every other backend.Workspace transition
flow-entry,flow-filter,nat,net,pipeline,routing,stats) to consumedataplane_concurrency::sync::{Arc, Mutex, RwLock, atomic, ...}instead ofstd::sync::*/parking_lot::*directly..unwrap()/.expect("poisoned")noise theLockResult-shaped surface forced, since the facade returns naked guards. Same shape as the existingparking_lot::Mutexconsumers.concurrency_mode(std)annotations at QSBR-touching test sites innat::stateful::apalloc::test_alloc,routing::fib::test, andflow-entry::flow_table::table::testskeep the existing std-only feature gating intact.nat::portfw::flow_state::get_packet_port_fw_statesimplification: the explicitdrop(guard)dance beforedebug!calls is removed (per the recorded author intent that it isn't worth the code complexity); the guard now drops implicitly at the end of scope.The sweep is mostly mechanical (
userewrites and.unwrap()removal). The semantic change is one step: every consumer now gets the facade-routed primitives, so flipping the loom/shuttle features at the top of the workspace is now meaningful.PR 2 of 3 in the concurrency facade stack (the original 4-PR plan collapsed the shuttle-only and loom-only PRs into this one because the wrappers, the workspace sweep, and the consumer changes can't be independently merged without leaving CI red). Depends on #1539 -- absorb-quiescent. The follow-on PR (#1544) adds
#[concurrency::test], thestressdispatcher, and thethread::scopeshim.Test plan
cargo build --workspacepasses.cargo nextest run --workspacepasses.cargo nextest run -p dataplane-concurrency --no-default-featurespasses (the std-backend path).cargo nextest run -p dataplane-concurrencypasses (parking_lot default).cargo nextest run -p dataplane-concurrency --features shuttlepasses.cargo nextest run -p dataplane-concurrency --features loom --releasepasses.cargo build -p dataplane-concurrency --no-default-features --features _strict_provenancecompiles viastd_backend.cargo clippy --workspace --all-targetspasses.check/miri/powerpc64passes (covers the_strict_provenancestrict-provenance path).features/release/shuttlepasses.features/release/loompasses.