You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(driver): tolerate closed-budget BrokenPipe in uring service_groups - #67
Persistent::service_groups in the io_uring driver (crates/bemo/src/driver/uring.rs) republishes returned/checked-out receive-buffer slots on each poll. When a slot is still checked out (delivered to the app and retained), publish allocates replacement storage through the pool's Budget. A closed workload budget makes Budget::reserve return BrokenPipe, but service_groups only tolerated WouldBlock/OutOfMemory — so the BrokenPipe escaped through prepare into Driver::fail, permanently poisoning the shared driver for every workload sharing it. Subsequent polls returned INVALID, try_shutdown failed, and Drop leaked the persistent slab.
This violated the documented ABI contract: closing a workload should leave "other workloads sharing its drivers untouched." The BrokenPipe-on-close convention is already handled gracefully elsewhere (Buffer::response, Pool::take, Pool::admit); only service_groups omitted it.
Fix: Added io::ErrorKind::BrokenPipe to the tolerated error kinds in both the grow and publish match arms of service_groups, so a closed budget stops the closing group from republishing without failing the driver. Affected layers: Rust io_uring driver only; no ABI, JVM, or header changes.
Validation
New unit tests in crates/bemo/src/driver.rs (Linux io_uring, #[cfg_attr(miri, ignore)]):
closing_a_workload_budget_with_a_checked_out_buffer_does_not_poison_the_shared_driver — receives a chunk, holds the buffer (slot checked out), closes the budget, polls, and asserts the driver stays healthy and try_shutdown completes cleanly with budget.used() == 0. Confirmed to fail with Kind(BrokenPipe) on the unmodified code.
persistent_receive_republishes_normally_while_the_workload_budget_is_open — the healthy A/B baseline confirming replacement allocation, a second receive, and clean shutdown while the budget stays open.
cargo nextest run -p bemo --locked --lib (31 driver/buffer unit tests) and integration tests for driver, workload, abi, pool, buffers, serving, http, and driver_churn all pass.
Backend sweep: forced io_uring (ELIDE_TRANSPORT_TEST_BACKEND=io-uring) and forced epoll (ELIDE_TRANSPORT_TEST_BACKEND=epoll) — both clean except two pre-existing test failures unrelated to this fix (confirmed by baseline runs via git stash).
Miri (cargo miri test -p bemo --no-default-features --features rust-allocator --lib --test buffers --test abi --test pool --test workload): 0 errors.
ASan and TSan via tools/verify.py (Elide LLVM toolchain, --stages rust): 386 tests, 0 sanitizer violations, only the same two pre-existing assertion failures.
Fuzz replay (cargo test --manifest-path fuzz/Cargo.toml --lib --locked): 7/7 pass.
make build and make test-native-image: Rust + JVM builds succeed; the C API contract (CapiContract) and gzip contract (ReusableGzipTest) pass through the native-image-built binaries. The CapiTlsChannelTest native-image build could not complete due to a pre-existing BouncyCastle classpath/OOM gap in tools/build.py::test_transport (BouncyCastle is not declared in bemo's build configuration) — unrelated to this Rust-only fix.
❌ Patch coverage is 94.44444% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.85%. Comparing base (26fb123) to head (108eee4).
✅ All tests successful. No failed tests found.
Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.
For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.
⚠️6 benchmarks spent significant time in system calls
System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.
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
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.
Detail bug report: View on Detail
Change
Closes #60
Persistent::service_groupsin the io_uring driver (crates/bemo/src/driver/uring.rs) republishes returned/checked-out receive-buffer slots on each poll. When a slot is still checked out (delivered to the app and retained),publishallocates replacement storage through the pool'sBudget. A closed workload budget makesBudget::reservereturnBrokenPipe, butservice_groupsonly toleratedWouldBlock/OutOfMemory— so theBrokenPipeescaped throughprepareintoDriver::fail, permanently poisoning the shared driver for every workload sharing it. Subsequent polls returnedINVALID,try_shutdownfailed, andDropleaked the persistent slab.This violated the documented ABI contract: closing a workload should leave "other workloads sharing its drivers untouched." The
BrokenPipe-on-close convention is already handled gracefully elsewhere (Buffer::response,Pool::take,Pool::admit); onlyservice_groupsomitted it.Fix: Added
io::ErrorKind::BrokenPipeto the tolerated error kinds in both thegrowandpublishmatch arms ofservice_groups, so a closed budget stops the closing group from republishing without failing the driver. Affected layers: Rust io_uring driver only; no ABI, JVM, or header changes.Validation
crates/bemo/src/driver.rs(Linux io_uring,#[cfg_attr(miri, ignore)]):closing_a_workload_budget_with_a_checked_out_buffer_does_not_poison_the_shared_driver— receives a chunk, holds the buffer (slot checked out), closes the budget, polls, and asserts the driver stays healthy andtry_shutdowncompletes cleanly withbudget.used() == 0. Confirmed to fail withKind(BrokenPipe)on the unmodified code.persistent_receive_republishes_normally_while_the_workload_budget_is_open— the healthy A/B baseline confirming replacement allocation, a second receive, and clean shutdown while the budget stays open.cargo build --workspace --locked,cargo clippy -p bemo --all-targets --locked -- -D warnings, andcargo fmt -p bemo --checkall pass.cargo nextest run -p bemo --locked --lib(31 driver/buffer unit tests) and integration tests for driver, workload, abi, pool, buffers, serving, http, and driver_churn all pass.ELIDE_TRANSPORT_TEST_BACKEND=io-uring) and forced epoll (ELIDE_TRANSPORT_TEST_BACKEND=epoll) — both clean except two pre-existing test failures unrelated to this fix (confirmed by baseline runs viagit stash).cargo miri test -p bemo --no-default-features --features rust-allocator --lib --test buffers --test abi --test pool --test workload): 0 errors.tools/verify.py(Elide LLVM toolchain,--stages rust): 386 tests, 0 sanitizer violations, only the same two pre-existing assertion failures.cargo test --manifest-path fuzz/Cargo.toml --lib --locked): 7/7 pass.make buildandmake test-native-image: Rust + JVM builds succeed; the C API contract (CapiContract) and gzip contract (ReusableGzipTest) pass through the native-image-built binaries. TheCapiTlsChannelTestnative-image build could not complete due to a pre-existing BouncyCastle classpath/OOM gap intools/build.py::test_transport(BouncyCastle is not declared in bemo's build configuration) — unrelated to this Rust-only fix.Automatic Fixes PRs can be configured here.