Skip to content

Commit ca74d6d

Browse files
authored
Fix potential leaks for interrupted dlpack capsule creation (#1291)
Signed-off-by: Kamil Tokarski <ktokarski@nvidia.com>
1 parent f0af76d commit ca74d6d

File tree

2 files changed

+103
-37
lines changed

2 files changed

+103
-37
lines changed

cuda_core/cuda/core/experimental/_dlpack.pyx

Lines changed: 90 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,54 @@ cdef void pycapsule_deleter(object capsule) noexcept:
2626

2727

2828
cdef void deleter(DLManagedTensor* tensor) noexcept with gil:
29-
stdlib.free(tensor.dl_tensor.shape)
30-
if tensor.manager_ctx:
31-
cpython.Py_DECREF(<object>tensor.manager_ctx)
32-
tensor.manager_ctx = NULL
33-
stdlib.free(tensor)
29+
if tensor:
30+
if tensor.dl_tensor.shape:
31+
stdlib.free(tensor.dl_tensor.shape)
32+
if tensor.manager_ctx:
33+
cpython.Py_DECREF(<object>tensor.manager_ctx)
34+
tensor.manager_ctx = NULL
35+
stdlib.free(tensor)
3436

3537

3638
cdef void versioned_deleter(DLManagedTensorVersioned* tensor) noexcept with gil:
37-
stdlib.free(tensor.dl_tensor.shape)
38-
if tensor.manager_ctx:
39-
cpython.Py_DECREF(<object>tensor.manager_ctx)
40-
tensor.manager_ctx = NULL
41-
stdlib.free(tensor)
42-
43-
44-
cpdef object make_py_capsule(object buf, bint versioned):
45-
cdef DLManagedTensor* dlm_tensor
46-
cdef DLManagedTensorVersioned* dlm_tensor_ver
47-
cdef DLTensor* dl_tensor
48-
cdef void* tensor_ptr
49-
cdef const char* capsule_name
50-
51-
if versioned:
39+
if tensor:
40+
if tensor.dl_tensor.shape:
41+
stdlib.free(tensor.dl_tensor.shape)
42+
if tensor.manager_ctx:
43+
cpython.Py_DECREF(<object>tensor.manager_ctx)
44+
tensor.manager_ctx = NULL
45+
stdlib.free(tensor)
46+
47+
48+
cdef inline DLManagedTensorVersioned* allocate_dlm_tensor_versioned() except? NULL:
49+
cdef DLManagedTensorVersioned* dlm_tensor_ver = NULL
50+
try:
5251
dlm_tensor_ver = <DLManagedTensorVersioned*>(
5352
stdlib.malloc(sizeof(DLManagedTensorVersioned)))
54-
dlm_tensor_ver.version.major = DLPACK_MAJOR_VERSION
55-
dlm_tensor_ver.version.minor = DLPACK_MINOR_VERSION
56-
dlm_tensor_ver.manager_ctx = <void*>buf
57-
dlm_tensor_ver.deleter = versioned_deleter
58-
dlm_tensor_ver.flags = 0
59-
dl_tensor = &dlm_tensor_ver.dl_tensor
60-
tensor_ptr = dlm_tensor_ver
61-
capsule_name = DLPACK_VERSIONED_TENSOR_UNUSED_NAME
62-
else:
53+
dlm_tensor_ver.dl_tensor.shape = NULL
54+
dlm_tensor_ver.manager_ctx = NULL
55+
return dlm_tensor_ver
56+
except:
57+
if dlm_tensor_ver:
58+
stdlib.free(dlm_tensor_ver)
59+
raise
60+
61+
62+
cdef inline DLManagedTensor* allocate_dlm_tensor() except? NULL:
63+
cdef DLManagedTensor* dlm_tensor = NULL
64+
try:
6365
dlm_tensor = <DLManagedTensor*>(
6466
stdlib.malloc(sizeof(DLManagedTensor)))
65-
dl_tensor = &dlm_tensor.dl_tensor
66-
dlm_tensor.manager_ctx = <void*>buf
67-
dlm_tensor.deleter = deleter
68-
tensor_ptr = dlm_tensor
69-
capsule_name = DLPACK_TENSOR_UNUSED_NAME
67+
dlm_tensor.dl_tensor.shape = NULL
68+
dlm_tensor.manager_ctx = NULL
69+
return dlm_tensor
70+
except:
71+
if dlm_tensor:
72+
stdlib.free(dlm_tensor)
73+
raise
7074

71-
dl_tensor.data = <void*><intptr_t>(int(buf.handle))
75+
76+
cdef inline int setup_dl_tensor_layout(DLTensor* dl_tensor, object buf) except -1:
7277
dl_tensor.ndim = 1
7378
cdef int64_t* shape_strides = \
7479
<int64_t*>stdlib.malloc(sizeof(int64_t) * 2)
@@ -77,7 +82,10 @@ cpdef object make_py_capsule(object buf, bint versioned):
7782
dl_tensor.shape = shape_strides
7883
dl_tensor.strides = NULL
7984
dl_tensor.byte_offset = 0
85+
return 0
86+
8087

88+
cdef inline int setup_dl_tensor_device(DLTensor* dl_tensor, object buf) except -1:
8189
cdef DLDevice* device = &dl_tensor.device
8290
# buf should be a Buffer instance
8391
if buf.is_device_accessible and not buf.is_host_accessible:
@@ -91,14 +99,59 @@ cpdef object make_py_capsule(object buf, bint versioned):
9199
device.device_id = 0
92100
else: # not buf.is_device_accessible and not buf.is_host_accessible
93101
raise BufferError("invalid buffer")
102+
return 0
94103

104+
105+
cdef inline int setup_dl_tensor_dtype(DLTensor* dl_tensor) except -1 nogil:
95106
cdef DLDataType* dtype = &dl_tensor.dtype
96107
dtype.code = <uint8_t>kDLInt
97108
dtype.lanes = <uint16_t>1
98109
dtype.bits = <uint8_t>8
110+
return 0
111+
99112

100-
cpython.Py_INCREF(buf)
101-
return cpython.PyCapsule_New(tensor_ptr, capsule_name, pycapsule_deleter)
113+
cpdef object make_py_capsule(object buf, bint versioned):
114+
cdef DLManagedTensor* dlm_tensor = NULL
115+
cdef DLManagedTensorVersioned* dlm_tensor_ver = NULL
116+
cdef DLTensor* dl_tensor
117+
cdef void* tensor_ptr
118+
cdef const char* capsule_name
119+
cdef object ret = None
120+
121+
try:
122+
if versioned:
123+
dlm_tensor_ver = allocate_dlm_tensor_versioned()
124+
# Transfer the reference to manager_ctx
125+
cpython.Py_INCREF(buf)
126+
dlm_tensor_ver.manager_ctx = <void*>buf
127+
dlm_tensor_ver.deleter = versioned_deleter
128+
dlm_tensor_ver.version.major = DLPACK_MAJOR_VERSION
129+
dlm_tensor_ver.version.minor = DLPACK_MINOR_VERSION
130+
dlm_tensor_ver.flags = 0
131+
dl_tensor = &dlm_tensor_ver.dl_tensor
132+
tensor_ptr = dlm_tensor_ver
133+
capsule_name = DLPACK_VERSIONED_TENSOR_UNUSED_NAME
134+
else:
135+
dlm_tensor = allocate_dlm_tensor()
136+
# Transfer the reference to manager_ctx
137+
cpython.Py_INCREF(buf)
138+
dlm_tensor.manager_ctx = <void*>buf
139+
dlm_tensor.deleter = deleter
140+
dl_tensor = &dlm_tensor.dl_tensor
141+
tensor_ptr = dlm_tensor
142+
capsule_name = DLPACK_TENSOR_UNUSED_NAME
143+
144+
dl_tensor.data = <void*><intptr_t>(int(buf.handle))
145+
setup_dl_tensor_layout(dl_tensor, buf)
146+
setup_dl_tensor_device(dl_tensor, buf)
147+
setup_dl_tensor_dtype(dl_tensor)
148+
ret = cpython.PyCapsule_New(tensor_ptr, capsule_name, pycapsule_deleter)
149+
except:
150+
if ret is None:
151+
deleter(dlm_tensor)
152+
versioned_deleter(dlm_tensor_ver)
153+
raise
154+
return ret
102155

103156

104157
class DLDeviceType(IntEnum):

cuda_core/tests/test_memory.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,19 @@ def test_buffer_dunder_dlpack_device_failure():
278278
buffer.__dlpack_device__()
279279

280280

281+
def test_buffer_dlpack_failure_clean_up():
282+
dummy_mr = NullMemoryResource()
283+
buffer = dummy_mr.allocate(size=1024)
284+
before = sys.getrefcount(buffer)
285+
with pytest.raises(BufferError, match="invalid buffer"):
286+
buffer.__dlpack__()
287+
after = sys.getrefcount(buffer)
288+
# we use the buffer refcount as sentinel for proper clean-up here,
289+
# hoping that malloc and frees did the right thing
290+
# as they are handled by the same deleter
291+
assert after == before
292+
293+
281294
@pytest.mark.parametrize("use_device_object", [True, False])
282295
def test_device_memory_resource_initialization(use_device_object):
283296
"""Test that DeviceMemoryResource can be initialized successfully.

0 commit comments

Comments
 (0)