Skip to content

feat(concurrency): poison-as-panic wrapper for std::sync#1540

Closed
daniel-noland wants to merge 1 commit into
pr/daniel-noland/concurrency-stack/1-absorb-quiescentfrom
pr/daniel-noland/concurrency-stack/2-facade-std
Closed

feat(concurrency): poison-as-panic wrapper for std::sync#1540
daniel-noland wants to merge 1 commit into
pr/daniel-noland/concurrency-stack/1-absorb-quiescentfrom
pr/daniel-noland/concurrency-stack/2-facade-std

Conversation

@daniel-noland
Copy link
Copy Markdown
Collaborator

@daniel-noland daniel-noland commented May 15, 2026

Summary

  • 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, all Send/Sync/Default/From/Display impls forward to the wrapped type.
  • 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.
  • concurrency::quiescent migrates from std::sync::{Arc, Mutex} to crate::sync::{Arc, Mutex} so the QSBR primitive consumes the facade.

PR 2 of 6. Depends on #1539 — absorb-quiescent. The next PR layers parking_lot as the default backend on top of this.

@daniel-noland daniel-noland force-pushed the pr/daniel-noland/concurrency-stack/2-facade-std branch from c712d12 to 8c6ea48 Compare May 15, 2026 21:17
`std::sync::{Mutex, RwLock}` return `LockResult<Guard>` because they
poison on holder panic. Call sites that don't model recovery -- and
this workspace doesn't -- end up sprinkling `.unwrap()` /
`.expect("poisoned")` everywhere to satisfy the type system.

Replaces `pub use std::sync` with a thin module that wraps
`std::sync::{Mutex, RwLock}` in newtypes whose `lock()` /
`read()` / `write()` return naked guards. Poison is treated as a
fatal invariant violation and panics in a cold helper.

* The wrapper is `#[repr(transparent)]`-equivalent: it carries
  `MutexGuard<'a, T>` / `RwLockReadGuard<'a, T>` etc. directly. The
  hot path is one move; the cold-on-poison branch panics with a
  uniform message.
* `Arc`, `Weak`, `atomic`, `mpsc`, `Once`, `OnceLock`, `Barrier`,
  `Condvar` etc. are still re-exported from `std::sync` -- only the
  poison-returning types are wrapped.
* `RwLock::upgradable_read` is implemented as exclusive `write()`
  with a `parking_lot`-shaped `upgrade()` API. std has no native
  upgradable read; subsequent backends (parking_lot) replace this
  with a true upgradable read. The surface stays consistent.
* `T: Sized` on `RwLock` -- the lowest common denominator across the
  backends that will land in subsequent PRs.

`concurrency/src/quiescent.rs` is the only in-tree caller that takes
a `Mutex`; its two `.expect("qsbr mutex poisoned")` call sites get
the naked guard directly under the default backend. Loom and shuttle
remain on their raw `LockResult`-based primitives at this point in
the stack; the call sites cfg-dispatch the `.expect` under those
backends until later PRs extend the wrapper to them.

`concurrency/src/slot.rs` doesn't need changes: its `Mutex<Arc<T>>`
fallback branch only fires under `loom`/`shuttle`/`_strict_provenance`,
where the wrapper isn't applied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
@daniel-noland daniel-noland force-pushed the pr/daniel-noland/concurrency-stack/1-absorb-quiescent branch from 7672b6e to c87ff7f Compare May 15, 2026 21:22
@daniel-noland daniel-noland force-pushed the pr/daniel-noland/concurrency-stack/2-facade-std branch from 8c6ea48 to 734ff09 Compare May 15, 2026 21:22
@daniel-noland
Copy link
Copy Markdown
Collaborator Author

Folded into #1542 — the std-sync wrapper, parking_lot default, and consumer sweep can't be split without leaving the workspace uncompilable between them, so this stack collapses 2+3+4 into a single PR.

@daniel-noland daniel-noland deleted the pr/daniel-noland/concurrency-stack/2-facade-std branch May 15, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant