NVIDIA / NVIDIA/cuda-python

Inconsistent lifetime management across `from_handle` / `from_*` APIs

Open
#1,989 10 comments 0 reactions 1 assignee View on GitHub

@Andy-Jost is already working on this.

Since May 1, 2026.

cuda.core enhancement P1
Dominant language
Cython
Stars
3.4k
Forks
329
Avg merge
1d 23h
Merged PRs (30d)
116

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.