diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e7db9102e..e37ae705527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This release is compatible with NumPy 2.5. * Added support for free-threaded Python builds [gh-3026](https://github.com/IntelPython/dpnp/pull/3026) * Added `dpnp.broadcast` class implementation [#2901](https://github.com/IntelPython/dpnp/pull/2901) * Added the `ndmax` keyword to `dpnp.array` for compatibility with NumPy [#3044](https://github.com/IntelPython/dpnp/pull/3044) +* Added `UsmNDArray_RemoveQueueRef` C API function to release a queue reference obtained from `UsmNDArray_GetQueueRef` [#3042](https://github.com/IntelPython/dpnp/pull/3042) ### Changed @@ -92,6 +93,7 @@ This release is compatible with NumPy 2.5. * Released the GIL before the blocking OneMKL DFT calls in the FFT extension [#3040](https://github.com/IntelPython/dpnp/pull/3040) * Fixed `astype` casting an out-of-range floating point value to a signed narrow integer type saturating to the destination min/max instead of wrapping like NumPy, generalizing the earlier unsigned-only fix [#3033](https://github.com/IntelPython/dpnp/pull/3033) * Fixed `dpnp.insert` silently ignoring out-of-bounds negative indices in a multi-element `obj`, so a mix of in-bounds and out-of-bounds indices now consistently raises `IndexError` [#3041](https://github.com/IntelPython/dpnp/pull/3041) +* Fixed a per-call `sycl::queue` leak in `usm_ndarray::get_queue()`/`get_device()` [#3042](https://github.com/IntelPython/dpnp/pull/3042) ### Security diff --git a/dpnp/include/dpnp4pybind11.hpp b/dpnp/include/dpnp4pybind11.hpp index d80dac8be2b..caed73b7316 100644 --- a/dpnp/include/dpnp4pybind11.hpp +++ b/dpnp/include/dpnp4pybind11.hpp @@ -88,6 +88,7 @@ class dpnp_capi int (*UsmNDArray_GetElementSize_)(PyUSMArrayObject *); int (*UsmNDArray_GetFlags_)(PyUSMArrayObject *); DPCTLSyclQueueRef (*UsmNDArray_GetQueueRef_)(PyUSMArrayObject *); + void (*UsmNDArray_RemoveQueueRef_)(DPCTLSyclQueueRef); py::ssize_t (*UsmNDArray_GetOffset_)(PyUSMArrayObject *); PyObject *(*UsmNDArray_GetUSMData_)(PyUSMArrayObject *); void (*UsmNDArray_SetWritableFlag_)(PyUSMArrayObject *, int); @@ -186,8 +187,9 @@ class dpnp_capi UsmNDArray_GetNDim_(nullptr), UsmNDArray_GetShape_(nullptr), UsmNDArray_GetStrides_(nullptr), UsmNDArray_GetTypenum_(nullptr), UsmNDArray_GetElementSize_(nullptr), UsmNDArray_GetFlags_(nullptr), - UsmNDArray_GetQueueRef_(nullptr), UsmNDArray_GetOffset_(nullptr), - UsmNDArray_GetUSMData_(nullptr), UsmNDArray_SetWritableFlag_(nullptr), + UsmNDArray_GetQueueRef_(nullptr), UsmNDArray_RemoveQueueRef_(nullptr), + UsmNDArray_GetOffset_(nullptr), UsmNDArray_GetUSMData_(nullptr), + UsmNDArray_SetWritableFlag_(nullptr), UsmNDArray_MakeSimpleFromMemory_(nullptr), UsmNDArray_MakeSimpleFromPtr_(nullptr), UsmNDArray_MakeFromPtr_(nullptr), USM_ARRAY_C_CONTIGUOUS_(0), @@ -215,6 +217,7 @@ class dpnp_capi this->UsmNDArray_GetElementSize_ = UsmNDArray_GetElementSize; this->UsmNDArray_GetFlags_ = UsmNDArray_GetFlags; this->UsmNDArray_GetQueueRef_ = UsmNDArray_GetQueueRef; + this->UsmNDArray_RemoveQueueRef_ = UsmNDArray_RemoveQueueRef; this->UsmNDArray_GetOffset_ = UsmNDArray_GetOffset; this->UsmNDArray_GetUSMData_ = UsmNDArray_GetUSMData; this->UsmNDArray_SetWritableFlag_ = UsmNDArray_SetWritableFlag; @@ -489,8 +492,16 @@ class usm_ndarray : public py::object PyUSMArrayObject *raw_ar = usm_array_ptr(); auto const &api = detail::dpnp_capi::get(); + // UsmNDArray_GetQueueRef_ returns an owning copy; free it through + // RemoveQueueRef (dpctl's DPCTLQueue_Delete) DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - return *(reinterpret_cast(QRef)); + auto qref_deleter = [](sycl::queue *q) { + detail::dpnp_capi::get().UsmNDArray_RemoveQueueRef_( + reinterpret_cast(q)); + }; + std::unique_ptr q_ptr{ + reinterpret_cast(QRef), qref_deleter}; + return *q_ptr; } sycl::device get_device() const @@ -498,8 +509,16 @@ class usm_ndarray : public py::object PyUSMArrayObject *raw_ar = usm_array_ptr(); auto const &api = detail::dpnp_capi::get(); + // UsmNDArray_GetQueueRef_ returns an owning copy; free it through + // RemoveQueueRef (dpctl's DPCTLQueue_Delete) DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - return reinterpret_cast(QRef)->get_device(); + auto qref_deleter = [](sycl::queue *q) { + detail::dpnp_capi::get().UsmNDArray_RemoveQueueRef_( + reinterpret_cast(q)); + }; + std::unique_ptr q_ptr{ + reinterpret_cast(QRef), qref_deleter}; + return q_ptr->get_device(); } int get_typenum() const diff --git a/dpnp/tensor/_usmarray.pyx b/dpnp/tensor/_usmarray.pyx index 9f85c71d546..78de01b936c 100644 --- a/dpnp/tensor/_usmarray.pyx +++ b/dpnp/tensor/_usmarray.pyx @@ -1850,6 +1850,12 @@ cdef api c_dpctl.DPCTLSyclQueueRef UsmNDArray_GetQueueRef(usm_ndarray arr): return arr.get_queue_ref() +cdef api void UsmNDArray_RemoveQueueRef(c_dpctl.DPCTLSyclQueueRef QRef): + """Delete a DPCTLSyclQueueRef previously returned by + UsmNDArray_GetQueueRef""" + c_dpctl.DPCTLQueue_Delete(QRef) # safe on NULL + + cdef api Py_ssize_t UsmNDArray_GetOffset(usm_ndarray arr): """Get offset of zero-index array element from the beginning of the USM allocation""" diff --git a/dpnp/tests/tensor/test_usm_ndarray_capi.py b/dpnp/tests/tensor/test_usm_ndarray_capi.py index 5ceb25fe7ec..d0e44f6712d 100644 --- a/dpnp/tests/tensor/test_usm_ndarray_capi.py +++ b/dpnp/tests/tensor/test_usm_ndarray_capi.py @@ -229,8 +229,19 @@ def test_pyx_capi_get_queue_ref(): fn_restype=ctypes.c_void_p, fn_argtypes=(ctypes.py_object,), ) + remove_queue_ref_fn = _pyx_capi_fnptr_to_callable( + X, + "UsmNDArray_RemoveQueueRef", + b"void (DPCTLSyclQueueRef)", + fn_restype=None, + fn_argtypes=(ctypes.c_void_p,), + ) queue_ref = get_queue_ref_fn(X) # address of a copy, should be unequal assert queue_ref != X.sycl_queue.addressof_ref() + # UsmNDArray_GetQueueRef returns an owning copy; + # UsmNDArray_RemoveQueueRef must free it and be a no-op on NULL + remove_queue_ref_fn(queue_ref) + remove_queue_ref_fn(None) def test_pyx_capi_make_from_memory():