feat(bytesbuf)!: make MemoryShared thread-aware#539
Conversation
Add `ThreadAware` as a supertrait to `MemoryShared` so the abstraction requires and expresses the thread-awareness our thread-mobile providers already guarantee. This makes it possible to relocate erased/abstract memory providers correctly across threads. Rename and redesign `CallbackMemory` into `WrappingMemory<T, FReserve>`, which owns an `inner: T` where `T: MemoryShared` and forwards `relocate` to it. The reserve closure now receives the correctly-managed `inner` as an argument (`Fn(&T, usize) -> BytesBuf`) rather than owning thread-affine state itself; it may capture only inert configuration. Rewrite `OpaqueMemory` over `thread_aware::Arc<dyn MemoryShared, PerCore>` so each core receives an independently relocatable clone; `OpaqueMemory::new` now requires `impl MemoryShared + Clone`. Give the test providers `TransparentMemory` and `FixedBlockMemory` no-op `ThreadAware` impls (they hold no thread-affine mutable state). Update downstream: `http_extensions` drops its now-unnecessary `Unaware` wrapper around `OpaqueMemory` and `with_custom_memory` gains a `+ Clone` bound; `bytesbuf_io` test providers migrate to `WrappingMemory`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
There was a problem hiding this comment.
Pull request overview
This PR makes bytesbuf’s MemoryShared abstraction explicitly thread-aware by requiring ThreadAware, ensuring providers can relocate thread-affine state when moved across threads. It also updates the type-erasure and wrapping story (OpaqueMemory, WrappingMemory) and migrates downstream/examples/tests accordingly.
Changes:
MemorySharednow requiresThreadAware; test providers add no-opThreadAwareimpls.- Replaces
CallbackMemorywithWrappingMemory<T, FReserve>to wrap an innerMemorySharedand forward relocation correctly. - Reworks
OpaqueMemoryto usethread_aware::Arc<dyn MemoryShared, PerCore>(requiringClone) and updateshttp_extensions+bytesbuf_iotests/examples.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| crates/http_extensions/src/body/builder.rs | Drops Unaware wrapper and requires Clone for custom memory to support thread-aware type erasure. |
| crates/bytesbuf/src/mem/wrapping_memory.rs | Adds WrappingMemory wrapper type (closure-based reservation + relocation forwarding) with tests. |
| crates/bytesbuf/src/mem/testing/transparent.rs | Adds no-op ThreadAware impl for TransparentMemory. |
| crates/bytesbuf/src/mem/testing/fixed_block.rs | Adds no-op ThreadAware impl for FixedBlockMemory. |
| crates/bytesbuf/src/mem/opaque_memory.rs | Reimplements OpaqueMemory using thread_aware::Arc<..., PerCore> and forwards relocation. |
| crates/bytesbuf/src/mem/mod.rs | Removes CallbackMemory module/export and exports WrappingMemory. |
| crates/bytesbuf/src/mem/memory_shared.rs | Makes ThreadAware a required supertrait of MemoryShared and documents the expectation. |
| crates/bytesbuf/src/mem/callback_memory.rs | Deletes the old CallbackMemory implementation. |
| crates/bytesbuf/src/lib.rs | Updates crate-level docs/doctest example to use WrappingMemory. |
| crates/bytesbuf/README.md | Regenerates README example to use WrappingMemory. |
| crates/bytesbuf/examples/bb_optimal_path.rs | Migrates example from CallbackMemory to WrappingMemory with thread-aware structure. |
| crates/bytesbuf/examples/bb_has_memory_optimizing.rs | Migrates example from CallbackMemory to WrappingMemory with thread-aware structure. |
| crates/bytesbuf_io/src/testing/pending.rs | Updates tests to use WrappingMemory (and inner provider forwarding) under OpaqueMemory. |
| crates/bytesbuf_io/src/testing/null.rs | Updates tests to use WrappingMemory (and inner provider forwarding) under OpaqueMemory. |
| crates/bytesbuf_io/src/testing/fake_write.rs | Updates tests to use WrappingMemory (and inner provider forwarding) under OpaqueMemory. |
| crates/bytesbuf_io/src/testing/fake_read.rs | Updates tests to use WrappingMemory (and inner provider forwarding) under OpaqueMemory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #539 +/- ##
=======================================
Coverage 100.0% 100.0%
=======================================
Files 354 355 +1
Lines 26897 26908 +11
=======================================
+ Hits 26897 26908 +11 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Redesign `OpaqueMemory` so it owns a `Box<dyn MemoryShared>` and forwards both cloning and `ThreadAware` relocation to the wrapped provider, rather than materializing per-core clones via `thread_aware::Arc<dyn MemoryShared, PerCore>`. The wrapped provider (e.g. `GlobalPool`, which already manages per-core state internally) now decides for itself how to be thread-aware, so the adapter imposes no `PerCore` strategy of its own and never double-layers thread-awareness. Express the cloneability requirement on the trait itself: `MemoryShared` gains an object-safe `clone_boxed` method, blanket-provided for any `Clone` provider, so type-erased providers can be duplicated without knowing their concrete type. `OpaqueMemory::new` and `HttpBodyBuilder::with_custom_memory` therefore no longer need a separate `Clone` bound. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise the no-op `ThreadAware::relocate` on `TransparentMemory` and `FixedBlockMemory` so both are covered, verifying each provider stays usable after relocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…losures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| /// | ||
| /// [thread-aware]: https://docs.rs/thread_aware | ||
| #[derive(Clone, Debug)] | ||
| #[derive(Clone, Debug, thread_aware::ThreadAware)] |
There was a problem hiding this comment.
Small nit, it's a bit weird this is used (or rather not use'ed) inconsistently.
| /// # Thread awareness | ||
| /// | ||
| /// A memory provider shared across threads is expected to be [thread-aware][ThreadAware], so it can | ||
| /// relocate any thread-affine state when moved between threads and avoid contention on |
There was a problem hiding this comment.
What does that mean for me as a user, like what is thread-affine state I should relocate? It couldn't be any memory handed out?
| /// ``` | ||
| /// | ||
| /// For a complete implementation pattern, see `examples/bb_has_memory_optimizing.rs`. | ||
| pub struct WrappingMemory<T, FReserve> |
| /// # Inert closures | ||
| /// | ||
| /// The closure must capture only inert configuration (e.g. flags, sizes) and never any thread-affine | ||
| /// state such as another memory provider. Thread-affine state belongs in the wrapped `inner` |
There was a problem hiding this comment.
Would it be better to (also) say it should not capture any references or pointers? I find 'inert' configuration a bit of a weird term.
There was a problem hiding this comment.
Alternatively, it could say it should only capture primitive or 'stack only' state?
| use crate::mem::{Memory, MemoryShared}; | ||
|
|
||
| /// Wraps an inner [`MemoryShared`] provider, customizing reservations via a closure. | ||
| /// |
There was a problem hiding this comment.
Also, why would I need this? I feel the example below is a bit made up.
Summary
Makes the
bytesbufMemorySharedabstraction thread-aware, closing a design gap where our thread-mobile (Send) memory providers were expected to be thread-aware but the abstraction did not require or express it. A provider now owns the decision of how and when to be thread-aware, and any adapter around it simply forwards relocation rather than imposing its own strategy.Changes
MemorySharednow hasThreadAwareas a supertrait, so the abstraction requires and expresses thread-awareness. This makes it possible to relocate erased/abstract memory providers correctly across threads.MemorySharedgains an object-safeclone_boxedmethod, blanket-provided for anyCloneprovider, so a type-erased provider can be duplicated without knowing its concrete type. EveryMemorySharedprovider is therefore required to beClone.CallbackMemoryintoWrappingMemory<T, FReserve>. It owns aninner: TwhereT: MemorySharedand forwardsrelocateto it. The reserve closure now receives the correctly-managedinneras an argument (Fn(&T, usize) -> BytesBuf) rather than owning thread-affine state itself; it may capture only inert configuration.OpaqueMemoryto own aBox<dyn MemoryShared>, forwarding both cloning (viaclone_boxed) andThreadAwarerelocation to the wrapped provider. It imposes no thread-awareness strategy of its own, leaving that decision entirely to the wrapped provider (e.g.GlobalPool, which already manages per-core state internally), and each clone is an independent provider.TransparentMemoryandFixedBlockMemoryno-opThreadAwareimpls, since they hold no thread-affine mutable state.http_extensionsdrops its now-unnecessaryUnawarewrapper aroundOpaqueMemory;bytesbuf_iotest providers migrate toWrappingMemory.Breaking changes
MemorySharedgainedThreadAwareas a supertrait and a newclone_boxedmethod, so implementors must now also beThreadAware(andClone, which the blanket impl requires).CallbackMemoryis removed in favour ofWrappingMemory.Validation
just format,just readme, andjust spellcheckall clean.bytesbuf,bytesbuf_io, andhttp_extensions(unit + doctests), including new tests coveringWrappingMemory/OpaqueMemoryrelocation forwarding and the no-oprelocateon the test providers.-D warningsclean; all remaining dependents build.cargo mutants --in-diff) leaves no surviving mutants.