Skip to content

Replace OwningResourceAdaptor with a BackRef mixin - #1078

Merged
rapids-bot[bot] merged 25 commits into
rapidsai:mainfrom
nirandaperera:back-ref-mixin
Jun 22, 2026
Merged

Replace OwningResourceAdaptor with a BackRef mixin#1078
rapids-bot[bot] merged 25 commits into
rapidsai:mainfrom
nirandaperera:back-ref-mixin

Conversation

@nirandaperera

@nirandaperera nirandaperera commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Replaces the OwningResourceAdaptor<Resource, BackRef> wrapper with a slim BackRefMixin<BackRef> that RmmResourceAdaptor inherits directly. OwningResourceAdaptor wrapped RmmResourceAdaptor only to carry a weak_ptr/shared_ptr<BackRef> pair and promote weak→strong on copy. Its allocate/deallocate/property forwarding was redundant since RmmResourceAdaptor already satisfies the CCCL resource concept.

Back-reference contract

BackRefMixin enforces a strict lifetime contract: a back-reference must be installed via set_backref() before an instance is copied. Copying an uninstalled (or installed-but-expired) instance throws std::bad_weak_ptr. This makes accidental lifetime mistakes loud instead of silently producing a copy that no longer keeps its owner alive.

To make the contract impossible to violate by mistake, RmmResourceAdaptor's primary-resource constructor is now private. Only BufferResource (a friend) can construct one, and it installs the back-reference before the adaptor becomes observable. External callers obtain an adaptor exclusively via BufferResource::device_mr_adaptor(), so every adaptor a caller can reach is guaranteed to be back-referenced and safely copyable.

Changes

C++

  • New cpp/include/rapidsmpf/memory/back_ref_mixin.hpp: templated BackRefMixin<BackRef> with default-empty weak_/strong_, set_backref(), copy ctor/assignment that promote weak→strong (throwing std::bad_weak_ptr when uninstalled or expired), and owner-based operator==. The promote logic is inlined into the copy ctor/assignment (no helper).
  • Deleted cpp/include/rapidsmpf/memory/owning_resource_adaptor.hpp.
  • RmmResourceAdaptor now publicly inherits BackRefMixin<BufferResource>. Its primary-resource constructor is private; BufferResource is a friend and is the sole producer of new instances. operator== checks both shared state and back-reference identity.
  • HostMemoryResource and PinnedMemoryResource do not inherit the mixin (deferred); they remain freely copyable. Their operator== is pool-identity based (pinned) / always-equal (stateless host).
  • BufferResource's owning_mr_ is a plain RmmResourceAdaptor member (no std::optional). device_mr is threaded through the private constructor so owning_mr_ is initialized in the member-initializer list; BufferResource::create() installs the back-ref afterward via owning_mr_.set_backref(weak_from_this()) (device only).
  • New BufferResource::device_mr_adaptor(): exposes the internal device RmmResourceAdaptor directly (e.g. to query get_main_record() / current_allocated()), so callers no longer construct an RmmResourceAdaptor externally.
  • streaming::Context::from_options(...) first parameter changed from RmmResourceAdaptor mr to any_device_resource mr (a plain device MR), since external callers can no longer construct an RmmResourceAdaptor. The Context hands the device MR to its internal BufferResource, which wraps it for tracking.
  • Call sites (tests + benchmarks) updated to pass a vanilla memory resource to BufferResource::create() / Context::from_options() and to read stats through device_mr_adaptor() instead of creating and wrapping an adaptor themselves.

Python bindings

  • RmmResourceAdaptor (Cython) now extends DeviceMemoryResource instead of UpstreamResourceAdaptor. Its __init__ raises TypeError to mirror the C++ private-constructor policy; instances are produced internally via a new cdef _from_cpp(const cpp_RmmResourceAdaptor&) factory that copies a back-ref'd C++ adaptor. The get_upstream accessor is removed.
  • New BufferResource.device_mr_adaptor() (cpdef): returns a back-ref'd RmmResourceAdaptor whose copies keep the BufferResource alive. This is the only way to obtain an adaptor from Python.
  • streaming.core.context.Context.from_options(...) now expects mr: DeviceMemoryResource instead of RmmResourceAdaptor (wrapped internally), matching the C++ change.
  • Statistics.memory_profiling docs/examples updated to obtain the adaptor via BufferResource(...).device_mr_adaptor().
  • Tests/conftests updated: stop pre-wrapping device MRs in RmmResourceAdaptor; pass plain rmm.mr.* resources to BufferResource/Context.from_options and read stats via br.device_mr_adaptor().

Latent side effect

Statistics::create_memory_recorder calls cuda::mr::resource_cast<RmmResourceAdaptor>(&mr) on the resource passed in. Previously this cast failed for resources obtained from BufferResource::device_mr() (the contained type was OwningResourceAdaptor<…>), silently returning a no-op MemoryRecorder. After this PR the contained type is RmmResourceAdaptor, so the cast succeeds and memory profiling becomes active for BR-allocated memory. No tests were relying on the previous no-op behavior.

Signed-off-by: niranda perera <niranda.perera@gmail.com>
@nirandaperera
nirandaperera requested a review from a team as a code owner June 1, 2026 23:39
@nirandaperera nirandaperera changed the title adding backref mixin Replace OwningResourceAdaptor with a BackRef mixin Jun 1, 2026
@nirandaperera
nirandaperera requested a review from madsbk June 1, 2026 23:40
Signed-off-by: niranda perera <niranda.perera@gmail.com>
@nirandaperera nirandaperera added breaking Introduces a breaking change improvement Improves an existing functionality labels Jun 1, 2026
Signed-off-by: niranda perera <niranda.perera@gmail.com>
@madsbk

madsbk commented Jun 2, 2026

Copy link
Copy Markdown
Member

Thanks @nirandaperera, I think this is a good idea. My main motivation for a generic wrapper like OwningResourceAdaptor was that we could potentially upstream it to RMM. I suspect other libraries built on top of RMM will need something similar now that we have the owning any_resource type. But a fully generic wrapper does not compose well with resource_cast, which is a real issue.

Before we go deeper into this PR, could you also implement, or at least sketch, the same pattern for host_mr and pinned_mr (#1070)? It would be useful to see the full picture before we settle on the final design.

@nirandaperera

Copy link
Copy Markdown
Contributor Author

I already added the Pinned MR and Host MR changes in this PR. Is that what you asked @madsbk ? Or did you want to sketch the owning wrapper impl for them?

@madsbk

madsbk commented Jun 2, 2026

Copy link
Copy Markdown
Member

Or did you want to sketch the owning wrapper impl for them?

Yes, but on second thought, I think the trade-off is already pretty clear. It really comes down to one thing:

  • A templated wrapper like OwningResourceAdaptor is nice because it avoids modifying the existing resource types.
  • A mixin is nice because it composes naturally with cuda::mr::resource_cast.

And I agree that the mixin approach is probably preferable overall.

@nirandaperera

Copy link
Copy Markdown
Contributor Author

Or did you want to sketch the owning wrapper impl for them?

Yes, but on second thought, I think the trade-off is already pretty clear. It really comes down to one thing:

  • A templated wrapper like OwningResourceAdaptor is nice because it avoids modifying the existing resource types.
  • A mixin is nice because it composes naturally with cuda::mr::resource_cast.

And I agree that the mixin approach is probably preferable overall.

I agree. Even I didnt realize this resource_cast bug when I reviewed OwningResourceAdapter PR. Claude is nifty in that sense ;-) LOL

@wence- wence- left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core idea looks good, please apply some sanity to all of the docstrings and comments

Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/buffer_resource.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/host_memory_resource.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/host_memory_resource.hpp Outdated
Comment thread cpp/src/memory/buffer_resource.cpp Outdated
Comment thread cpp/tests/test_buffer_resource.cpp Outdated
Signed-off-by: niranda perera <niranda.perera@gmail.com>
@nirandaperera
nirandaperera requested a review from wence- June 3, 2026 19:14
Signed-off-by: niranda perera <niranda.perera@gmail.com>
@nirandaperera

Copy link
Copy Markdown
Contributor Author

@madsbk @wence- I updated the PR with better docstrings

Comment thread cpp/include/rapidsmpf/memory/host_memory_resource.hpp Outdated
@nirandaperera
nirandaperera requested a review from madsbk June 8, 2026 17:05
Signed-off-by: niranda perera <niranda.perera@gmail.com>
Signed-off-by: niranda perera <niranda.perera@gmail.com>
Signed-off-by: niranda perera <niranda.perera@gmail.com>
Signed-off-by: niranda perera <niranda.perera@gmail.com>
@nirandaperera
nirandaperera requested a review from a team as a code owner June 10, 2026 20:41
@nirandaperera
nirandaperera requested a review from jameslamb June 10, 2026 20:41
@nirandaperera
nirandaperera requested a review from a team as a code owner June 12, 2026 19:13
Signed-off-by: niranda perera <niranda.perera@gmail.com>
Comment thread cpp/include/rapidsmpf/memory/buffer_resource.hpp Outdated
Comment on lines +215 to +221
@@ -205,6 +218,7 @@ class BufferResource : public std::enable_shared_from_this<BufferResource> {
* @throws std::invalid_argument if no pinned memory resource is available.
* @return Reference to the RMM resource used for pinned host allocations.
*/
// TODO: returned ref will not keep the BufferResource alive

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like:

   /**
    * @brief Get the RMM pinned host memory resource.
    *
    * The returned reference does not by itself maintain the lifetime of this
    * BufferResource. See device_mr() for details.    
    *
    * @throws std::invalid_argument if no pinned memory resource is available.
    * @return Reference to the RMM resource used for pinned host allocations.
    */

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment thread cpp/include/rapidsmpf/memory/buffer_resource.hpp Outdated

@jameslamb jameslamb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this PR is still under active development and discussion. Please @ me for a ci-codeowners approval once you're ready for that.

@nirandaperera
nirandaperera requested a review from madsbk June 15, 2026 16:24
Comment on lines +22 to +29
* - **Uninstalled** (default-constructed): the instance is not bound to any
* owner. Copying an uninstalled instance throws `std::bad_weak_ptr`; a
* back-reference must be installed via `set_backref()` before copying.
* - **Installed** (after `set_backref()`): the instance is bound to a
* specific owner. Each copy of an installed instance acquires shared
* ownership of that owner for the lifetime of the copy. If the owner has
* been destroyed before the copy is made, copying throws
* `std::bad_weak_ptr`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix this description. How can an instance be bound to a specific owner and then the instance obtains via copy shared ownership of that owner?

How is the owner, who is the ownee?

@nirandaperera nirandaperera Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad! semantics changed since I made it mandatory to set a backref before copying, So, uninstalled state is no longer valid.

Comment on lines +31 to +33
* Move operations transfer state without re-acquiring ownership. Equality
* is owner-based: two instances compare equal iff they reference the same
* owner, or are both uninstalled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do I care about equality at all?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two back_ref objects will be equal if their weak ptrs point to the same control block. We cant simply use this == addressof(other) here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but what do I need equality for? What goes wrong if (for example) we delete operator==

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for current usages, we dont need it, because rmm resource adapter defines its own equality operator. But I wanted to preserve the default equlity operator behavior.
Eg:

class Foo: BackRefMixin<int>{
int a; 
bool operator==(const Foo&) const = default;
}

I think for this to work, we need BackRefMixin to define an equality opertor.

Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/back_ref_mixin.hpp Outdated
Comment thread cpp/include/rapidsmpf/memory/buffer_resource.hpp Outdated
Comment thread cpp/include/rapidsmpf/rmm_resource_adaptor.hpp Outdated
Comment thread cpp/tests/test_buffer_resource.cpp Outdated
Comment thread cpp/tests/test_buffer_resource.cpp Outdated
@wence-

wence- commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Please also apply some work to fix the PR description.

@nirandaperera
nirandaperera requested a review from wence- June 15, 2026 19:20

@wence- wence- left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor documentation suggestions

Comment thread cpp/include/rapidsmpf/memory/buffer_resource.hpp Outdated
Comment thread cpp/include/rapidsmpf/rmm_resource_adaptor.hpp Outdated
Comment thread cpp/include/rapidsmpf/rmm_resource_adaptor.hpp Outdated
@nirandaperera
nirandaperera requested a review from jameslamb June 16, 2026 22:32
@nirandaperera

Copy link
Copy Markdown
Contributor Author

@jameslamb This is ready

@jameslamb jameslamb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe this no longer needs ci-codeowners / packaging-codeowners but approving for you anyway.

I skimmed the changes and don't see any issues, deferring to the much more thorough and better-informed reviews given by other reviewers.

@nirandaperera

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 7964c11 into rapidsai:main Jun 22, 2026
66 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jun 23, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jun 24, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jul 19, 2026
… resources (#1106)

`HostMemoryResource` and `PinnedMemoryResource` are now **`BufferResource`-only** resources that carry a strict `BackRefMixin<BufferResource>` back-reference. When a copy of one of these resources is made (e.g. when CCCL promotes `host_mr()` / `pinned_mr()` into an owning `cuda::mr::any_resource` inside a `HostBuffer`), the copy promotes the stored `weak_ptr` to a `shared_ptr`, keeping the owning `BufferResource` alive for as long as any derived buffer lives. This closes a lifetime gap where a `Buffer` could outlive the `BufferResource` that produced its memory resource.


The mixin is *strict*: copying an instance without an installed back-reference throws `std::bad_weak_ptr`. To make that safe, these resources can no longer be constructed as standalone objects — they are created only by `BufferResource`, which installs the back-reference immediately after construction. This mirrors how `RmmResourceAdaptor` already works on this branch.

## C++ changes

- **`HostMemoryResource`**: inherits `BackRefMixin<BufferResource>`; constructor is private with `friend class BufferResource`. Equality remains stateless (`always true`) since instances are interchangeable and the back-reference is installed exactly once.
- **`PinnedMemoryResource`**: inherits `BackRefMixin<BufferResource>` as a second base; constructor is private (`friend class BufferResource`). The `make_if_available` / `from_options` factories were **removed** so no back-reference-less instance can ever escape. Equality compares the shared pool state only.
- **`PinnedPoolProperties`**: gained a `numa_id` field (defaults to the calling thread's NUMA node) so all pinned configuration flows as one struct. Added a free helper `pinned_pool_properties_from_options(...)`.
- **`PinnedMemoryDisabled`**: new `constexpr std::nullopt_t` sentinel used to disable pinned host memory (replaces the old `PinnedMemoryResource::Disabled`).
- **`BufferResource::create()` / `from_options()`**: now take `std::optional<PinnedPoolProperties> pinned_pool_properties` (default `PinnedMemoryDisabled`) instead of a pre-built resource. The pinned resource is constructed internally and the back-reference is installed on `owning_mr_`, `host_mr_`, and `pinned_mr_` before `create()` returns.
- **`try_pinned_mr()`**: now returns `std::optional<PinnedMemoryResource>` (a back-referenced handle) so Python/Statistics get a concrete, lifetime-safe handle.

## Python changes

- **`PinnedMemoryResource`**: now an opaque, non-constructible handle (`__init__` raises `TypeError`); obtained via `BufferResource.pinned_mr`.
- **`PinnedPoolProperties`**: new `@dataclass` (`initial_pool_size`, `max_pool_size`, `numa_id`) used to configure pinned memory.
- **`BufferResource(...)`**: replaces the old `pinned_mr=` argument with `pinned_pool_properties: PinnedPoolProperties | None = None` (`None` disables pinned host memory). `from_options` derives this from the config options.
- **`Statistics.report(pinned_mr=...)`**: unchanged signature; the handle is now sourced from `BufferResource.pinned_mr`.

## Tests

- Added `HostMrKeepsBufferResourceAlive` and `PinnedMrKeepsBufferResourceAlive` regression tests mirroring `DeviceMrKeepsBufferResourceAlive`.
- Routed all C++ tests/benchmarks through a `BufferResource` (no standalone Host/Pinned MR construction); reworked `test_host_buffer`, `test_config`, `test_memory_resources`, and `bench_memory_resources`.
- Updated Python `test_config.py` to configure pinned memory via `BufferResource` and assert on `BufferResource.pinned_mr`.

## Breaking changes

- C++: `HostMemoryResource` / `PinnedMemoryResource` can no longer be constructed directly; `PinnedMemoryResource::make_if_available` / `from_options` and the `Disabled` sentinel are removed. `BufferResource::create()` takes `PinnedPoolProperties` instead of a `PinnedMemoryResource`.
- Python: `PinnedMemoryResource(...)` is no longer constructible; the `BufferResource(pinned_mr=...)` argument is replaced by `pinned_pool_properties=`.


Depends on #1078

Closes #1070

Authors:
  - Niranda Perera (https://github.com/nirandaperera)

Approvers:
  - Mads R. B. Kristensen (https://github.com/madsbk)

URL: #1106
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Introduces a breaking change improvement Improves an existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants