fix(s2n-quic-core): use raw waker vtable for contract assertions - #3074
fix(s2n-quic-core): use raw waker vtable for contract assertions#3074camshaft wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes false-positive waker contract violations on weakly-ordered architectures (notably aarch64) by replacing an Arc::strong_count-based heuristic with explicit atomic counters maintained via a custom RawWaker vtable. It also adds optional debug context to improve diagnostics when the contract is violated.
Changes:
- Implement waker contract tracking using a custom
RawWakerVTableandclone/drop/wakecounters withAcquire/Releasesynchronization. - Extend the contract API with
assert_contract_with_context/debug_assert_contract_with_contextto attach optional diagnostic context to assertion failures. - Add a loom-modeled concurrent wake-and-check test and adjust
sync::primitivevisibility to support loom-instrumented primitives.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| quic/s2n-quic-core/src/task/waker/contract.rs | Reworks contract checking to use a custom RawWaker vtable + atomic counters; adds context-aware assertion APIs and a loom concurrency test. |
| quic/s2n-quic-core/src/sync.rs | Makes sync::primitive pub(crate) so internal modules (like the contract checker) can use loom-instrumented primitives in tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #[cfg(debug_assertions)] | ||
| return assert_contract(cx, f); | ||
| return assert_contract_with_context(cx, f); | ||
|
|
||
| #[cfg(not(debug_assertions))] | ||
| return f(cx); | ||
| return f(cx).0; |
There was a problem hiding this comment.
won't the compiler eliminate the computation of the context if it sees that it's never used anywhere?
There was a problem hiding this comment.
Depends on how the context is computed -- the compiler can't eliminate code that might have side effects for sure, but even side-effect-free code that it can't prove is side effect free (e.g. insufficient inlining won't get deleted).
There was a problem hiding this comment.
Fair - I can push a modification to disable it
379a1e9 to
86a9af0
Compare
|
|
||
| // Thread B (pusher): takes and wakes (simulating push -> take_waker -> wake) | ||
| let pusher_slot = slot.clone(); | ||
| let pusher = loom::thread::spawn(move || { |
There was a problem hiding this comment.
nit: I don't think you actually need a second thread, this can just happen in the 'main' thread? That seems like it might cut down runtimes within loom since there's less reordering possible (no need to consider differences between main thread which doesn't do any work and each of the child threads)?
Description of changes:
The waker contract checker used
Arc::strong_countto determine whether a waker had been cloned (stored for later waking). On ARM/aarch64,Arc::strong_countuses aRelaxedload internally. Under weak memory ordering, this allows the checker to observe the strong count decrement from a concurrent wake() + drop without yet observing the wake_called store (Release). This caused false contract violations that manifested as panics with debug assertions.The fix replaces the
Arc::strong_count+AtomicBoolapproach with explicit clone_count/drop_count/wake_count atomics tracked via a customRawWakervtable. The checker loads all counters withAcquireordering, which establishes a proper happens-before relationship with theReleasestores in the vtable functions. This eliminates the reordering window that ARM exposed.Additionally, check_outcome now accepts an optional debug context parameter (assert_contract_with_context / debug_assert_contract_with_context) so callers can attach diagnostic state to contract violations.
Call-outs:
sync::primitivevisibility was changed from mod to pub(crate) so the contract can use loom-instrumented Arc/AtomicU64 for testing.Arc::strong_countusesSeqCstinternally, which is stronger than real hardware. The loom test verifies correctness of the new implementation under all the interleavings it's able to, but doesn't guarantee full correctness.Testing:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.