Skip to content

Inconsistent lifetime management across from_handle / from_* APIs #1989

Description

@leofang

Summary

Audit of all public from_handle / from_* methods in cuda.core reveals several inconsistencies in how lifetime management is exposed to users. These should be resolved before the 1.0 stable release.

Inventory

Class Method Ownership Model
Stream from_handle(handle) Always non-owning (borrowed)
Kernel from_handle(handle, mod=) Non-owning ref; optional _keepalive holds ObjectCode
Buffer from_handle(ptr, size, mr=, owner=) 3 modes: non-owning / mr-deallocates / owner-ref
Event from_ipc_descriptor(...) Owns imported event
Buffer from_ipc_descriptor(...) Owns imported buffer
GraphicsResource from_gl_buffer(...) / from_gl_image(...) Owns GL registration
DeviceMemoryResource from_allocation_handle(...) / from_registry(uuid) Owns imported pool / retrieves existing
PinnedMemoryResource from_allocation_handle(...) / from_registry(uuid) Owns imported pool / retrieves existing
ObjectCode from_cubin/ptx/ltoir/fatbin/object/library() Owns module data
StridedMemoryView from_dlpack/cai/array_interface/buffer/any_interface() Always non-owning (view)

Inconsistencies

1. Stream.from_handle cannot opt into keeping an owner alive

  • Buffer.from_handle accepts owner= to hold a Python reference and prevent GC of the foreign allocator.
  • Kernel.from_handle accepts mod= to hold a reference and prevent GC of the parent ObjectCode/library.
  • Stream.from_handle has no equivalent parameter. It creates a borrowed stream with no way for the user to tie lifetime to an owner object.

The internal machinery already supports this — create_stream_handle_with_owner() in C++ accepts a Python owner and does Py_INCREF/DECREF. But from_handle() only passes the ephemeral _stream_holder wrapper as the owner, not the user's actual owner object.

Suggestion: Add an owner parameter to Stream.from_handle, consistent with Buffer.from_handle.

2. Event has no public from_handle()

  • Stream, Kernel, and Buffer all expose public from_handle() for wrapping foreign CUDA handles.
  • Event only has from_ipc_descriptor(). There is an internal _from_handle() (cdef), but no public way to wrap a foreign CUevent.

Suggestion: Add Event.from_handle(handle) for symmetry, with the same borrowed/non-owning semantics.

3. Kernel.from_handle docstring doesn't clearly document ownership

  • Stream.from_handle docstring explicitly says: "Stream lifetime is not managed, foreign object must remain alive while this stream is active."
  • Buffer.from_handle docstring explicitly documents all three ownership modes and when the pointer is/isn't freed.
  • Kernel.from_handle docstring mentions mod "provides library lifetime for foreign kernels" but doesn't clearly state that the kernel handle itself is non-owning and won't be destroyed on close/GC.

Suggestion: Improve the Kernel.from_handle docstring to match the clarity of the other two.

Additional observation

All from_handle() methods use @staticmethod, while from_ipc_descriptor() / from_gl_*() / from_allocation_handle() use @classmethod. This may be intentional (the latter group uses cls for subclassing support), but worth confirming as a deliberate convention and documenting if so.

-- Leo's bot

Activity

  1. added
    cuda.coreEverything related to the cuda.core module
    on Apr 29, 2026
  2. added this to the cuda.core v1.0.0 milestone on Apr 29, 2026
  3. leofang commented on Apr 29, 2026

    @leofang
    MemberAuthor

    Related: #1187

  4. leofang commented on Apr 29, 2026

    @leofang
    MemberAuthor

    @mdboom @Andy-Jost can either of you take this one too? 😛

  5. Andy-Jost commented on Apr 29, 2026

    @Andy-Jost
    Contributor

    Agreed this is inconsistent and messy. We should provide a uniform way of building core objects from external resources.

    The specific fixes outlined are purely additive, so I'd say lower priority for v1.0 release.

  6. Andy-Jost commented on Apr 29, 2026

    @Andy-Jost
    Contributor

    The @staticmethod/@classmethod inconsistency might be related to #1671 and #1660

  7. leofang commented on Apr 29, 2026

    @leofang
    MemberAuthor

    The specific fixes outlined are purely additive, so I'd say lower priority for v1.0 release.

    Oh, you meant no breaking change is needed? In that case let me set it to P1.

  8. added
    P1Medium priority - Should do
    on Apr 29, 2026
  9. mdboom commented on Apr 30, 2026

    @mdboom
    Contributor

    The @staticmethod/@classmethod inconsistency might be related to #1671 and #1660

    But from a user/API perspective that shouldn't matter, right? (Ignoring users who subclass our classes).

  10. Andy-Jost commented on May 4, 2026

    @Andy-Jost
    Contributor

    When updating *_with_owner functions to support Python ownership, consider #1976 (comment)

  11. Andy-Jost commented on May 4, 2026

    @Andy-Jost
    Contributor

    But from a user/API perspective that shouldn't matter, right? (Ignoring users who subclass our classes).

    Yes, this is an implementation detail only.

  12. Andy-Jost commented on Aug 3, 2026

    @Andy-Jost
    Contributor

    A fourth inconsistency in the same family: Buffer.from_handle(ptr, size, mr=mr) hands ownership to a memory resource with no way to name the stream that will order the eventual mr.deallocate(), even though stream has been required on allocate/deallocate since 1.0.0 — PR #2455 adds an optional stream argument that defaults to the current default stream.

  13. Andy-Jost commented on Aug 3, 2026

    @Andy-Jost
    Contributor

    Follow-up: once the optional stream argument has shipped, Buffer.from_handle(mr=...) should emit a DeprecationWarning when stream is omitted, so a future major version can require it like allocate/deallocate.

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

Metadata

Metadata

Assignees

Labels

P1Medium priority - Should docuda.coreEverything related to the cuda.core moduleenhancementAny code-related improvements

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions