@@ -17,7 +17,7 @@ from cuda.core._resource_handles cimport (
1717 ContextHandle,
1818 DevicePtrHandle,
1919 StreamHandle,
20- create_context_handle_ref ,
20+ retain_context_handle ,
2121 deviceptr_create_with_owner,
2222 deviceptr_create_with_mr,
2323 get_last_error,
@@ -28,7 +28,12 @@ from cuda.core._resource_handles cimport (
2828)
2929from cuda.core.typing import DevicePointerType
3030
31- from cuda.core._stream cimport Stream, Stream_accept, default_stream
31+ from cuda.core._stream cimport (
32+ Stream,
33+ Stream_accept,
34+ Stream_resolve_context,
35+ default_stream,
36+ )
3237from cuda.core._utils.cuda_utils cimport HANDLE_RETURN, _parse_fill_value
3338
3439import sys
@@ -55,10 +60,19 @@ cdef void _mr_dealloc_callback(
5560 """ Called by the C++ deleter to deallocate via MemoryResource.deallocate.
5661
5762 This is the C++ teardown path: there is no Python caller frame from
58- which to obtain a stream. The device-pointer handle therefore carries
59- the exact stream selected at allocation/adoption or by ``Buffer.close``.
60- An empty handle remains ``None``; cleanup must never invent ordering by
61- selecting a default stream at destruction time.
63+ which to obtain a stream, so the handle supplies the stream recorded at
64+ allocation/adoption or by ``Buffer.close`` and cleanup never picks one of
65+ its own. An empty handle stays ``None``, which is a warning-only path: a
66+ resource that cannot free without a stream raises, and the failure is
67+ reported below rather than propagated, because a destructor has nowhere to
68+ raise to.
69+
70+ The recorded stream may be a default-stream token. A token is resolved by
71+ the driver on the thread that runs the destructor, so under per-thread
72+ default streams the physical stream can differ from the one the buffer's
73+ owner used. Only the choice of stream is preserved; ordering work across
74+ threads remains the application's responsibility, as it is for the
75+ equivalent raw driver calls.
6276 """
6377 cdef object stream
6478 try :
@@ -76,13 +90,18 @@ __all__ = ['Buffer', 'MemoryResource']
7690
7791cdef ContextHandle _pointer_context_handle(
7892 cydriver.CUdeviceptr ptr,
79- bint* require_stream_context ,
93+ bint* is_stream_ordered ,
8094):
81- """ Return the pointer's owning context when CUDA reports one."""
95+ """ Return a handle for the pointer's owning context, if CUDA reports one.
96+
97+ The handle owns the context when the driver permits it (see
98+ ``retain_context_handle``), which for a primary context makes the
99+ allocation's context a genuine dependency of the buffer.
100+ """
82101 cdef ContextHandle h_context
83102 cdef cydriver.CUcontext context = NULL
84103 cdef cydriver.CUresult result
85- require_stream_context [0 ] = False
104+ is_stream_ordered [0 ] = False
86105 if ptr == 0 :
87106 return h_context
88107
@@ -94,9 +113,9 @@ cdef ContextHandle _pointer_context_handle(
94113 )
95114 if result == cydriver.CUDA_SUCCESS:
96115 if context != NULL :
97- return create_context_handle_ref (context)
116+ return retain_context_handle (context)
98117 # Stream-ordered allocations are context-independent.
99- require_stream_context [0 ] = True
118+ is_stream_ordered [0 ] = True
100119 return h_context
101120 if result in (
102121 cydriver.CUresult.CUDA_ERROR_INVALID_VALUE,
@@ -216,28 +235,38 @@ cdef class Buffer:
216235 When ``mr`` is provided , the buffer takes ownership: ``mr.deallocate()``
217236 is called when the buffer is closed or garbage collected. When ``owner``
218237 is provided , the owner is kept alive but no deallocation is performed.
238+
239+ An owning buffer always records a deallocation stream , defaulting to
240+ ``default_stream()``, so teardown never has to choose one at destruction
241+ time. Callers wanting different ordering must pass ``stream``.
219242 """
220243 if mr is not None and owner is not None:
221244 raise ValueError("owner and memory resource cannot be both specified together")
222245 cdef Buffer self = Buffer.__new__ (cls )
223246 cdef uintptr_t c_ptr = < uintptr_t> (int (ptr))
224- cdef ContextHandle h_context
247+ cdef ContextHandle h_allocation_context
248+ cdef ContextHandle h_release_context
225249 cdef StreamHandle h_stream
226250 cdef Stream s
227- cdef bint require_stream_context
251+ cdef bint is_stream_ordered
228252 if mr is not None:
229- if stream is not None:
230- s = Stream_accept(stream)
231- h_stream = s._h_stream
232- h_context = _pointer_context_handle(
233- c_ptr, & require_stream_context)
253+ s = Stream_accept(default_stream() if stream is None else stream)
254+ h_stream = s._h_stream
255+ h_allocation_context = _pointer_context_handle(
256+ c_ptr, & is_stream_ordered)
257+ h_release_context = Stream_resolve_context(s)
258+ if not h_release_context:
259+ h_release_context = h_allocation_context
260+ if is_stream_ordered and not h_release_context:
261+ HANDLE_RETURN(
262+ cydriver.CUresult.CUDA_ERROR_INVALID_CONTEXT )
234263 self._h_ptr = deviceptr_create_with_mr(
235264 c_ptr,
236265 size,
237266 mr,
238- h_context ,
267+ h_allocation_context ,
239268 h_stream,
240- require_stream_context ,
269+ h_release_context ,
241270 )
242271 if not self._h_ptr:
243272 HANDLE_RETURN(get_last_error())
@@ -289,18 +318,18 @@ cdef class Buffer:
289318 The reference is kept as long as the buffer is alive.
290319 The ``owner`` and ``mr`` cannot be specified together.
291320 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
292- Initial deallocation stream when ``mr`` owns the pointer. If
293- omitted , the current default stream is selected and retained now ,
294- rather than during eventual destruction.
321+ Deallocation stream to record when ``mr`` owns the pointer.
322+ Defaults to the calling thread's default stream , chosen here
323+ rather than at destruction time. Note that a default stream is
324+ recorded as a token: under per-thread default streams the driver
325+ resolves it against whichever thread runs the destructor.
295326
296327 Note
297328 ----
298329 When neither ``mr`` nor ``owner`` is specified , this creates a
299330 non-owning reference. The pointer will NOT be freed when the
300331 :class:`Buffer` is closed or garbage collected.
301332 """
302- if mr is not None and stream is None:
303- stream = default_stream()
304333 return Buffer._init(
305334 ptr , size , mr = mr, owner = owner, stream = stream)
306335
@@ -680,14 +709,19 @@ cdef Buffer Buffer_from_deviceptr_handle(
680709cdef inline void Buffer_close(Buffer self , object stream):
681710 """ Close a buffer, freeing its memory."""
682711 cdef Stream s
712+ cdef ContextHandle h_release_context
683713 if not self ._h_ptr:
684714 return
685715 # Update deallocation stream if provided
686716 if stream is not None :
687717 s = Stream_accept(stream)
718+ h_release_context = Stream_resolve_context(s)
719+ if not h_release_context:
720+ HANDLE_RETURN(
721+ cydriver.CUresult.CUDA_ERROR_INVALID_CONTEXT)
688722 with nogil:
689- HANDLE_RETURN( set_deallocation_stream(
690- self ._h_ptr, s._h_stream) )
723+ set_deallocation_stream(
724+ self ._h_ptr, s._h_stream, h_release_context )
691725 # Reset handle - RAII deleter will free the memory (and release owner ref in C++)
692726 self ._h_ptr.reset()
693727 self ._size = 0
0 commit comments