From cab52837a39eb6e0a1b7dd85b7cf2db106bffdeb Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 25 Aug 2026 09:30:30 -0700 Subject: [PATCH 1/6] Fix queue copy leak in dpnp4pybind11 get_queue()/get_device() --- dpnp/include/dpnp4pybind11.hpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dpnp/include/dpnp4pybind11.hpp b/dpnp/include/dpnp4pybind11.hpp index d80dac8be2b..2cc55648bf4 100644 --- a/dpnp/include/dpnp4pybind11.hpp +++ b/dpnp/include/dpnp4pybind11.hpp @@ -489,8 +489,13 @@ 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 (DPCTLQueue_Copy, + // i.e. `new sycl::queue`); wrap it in a unique_ptr to avoid leaking a + // queue per call. DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - return *(reinterpret_cast(QRef)); + std::unique_ptr q_ptr{ + reinterpret_cast(QRef)}; + return *q_ptr; } sycl::device get_device() const @@ -498,8 +503,12 @@ 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; wrap it in a + // unique_ptr to avoid leaking a queue per call. DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - return reinterpret_cast(QRef)->get_device(); + std::unique_ptr q_ptr{ + reinterpret_cast(QRef)}; + return q_ptr->get_device(); } int get_typenum() const From 2bd7421703ba6460c5d7a53f10148e1426aae267 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 25 Aug 2026 09:40:27 -0700 Subject: [PATCH 2/6] Add gh-3042 to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14d3145a58f..5dcc48eef59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,8 @@ This release is compatible with NumPy 2.5. * Released the GIL before the remaining blocking OneMKL BLAS and LAPACK calls to prevent host tasks contention, completing the work started in [#2850](https://github.com/IntelPython/dpnp/pull/2850) [#3027](https://github.com/IntelPython/dpnp/pull/3027) * Fixed `dpnp.repeat` raising an unclear `TypeError` for a nested sequence of `repeats` [#3024](https://github.com/IntelPython/dpnp/pull/3024) * Fixed `dpnp.ndarray.view` ignoring the USM element offset of a sliced array, which also caused `dpnp.einsum` to silently return wrong results for a single sliced operand with no summed index [#3037](https://github.com/IntelPython/dpnp/pull/3037) +* Fixed a per-call `sycl::queue` leak in `usm_ndarray::get_queue()`/`get_device()` [#3042](https://github.com/IntelPython/dpnp/pull/3042) + ### Security From 2f375826ba198b276294658133f75ec5e8f557cc Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 27 Aug 2026 04:00:55 -0700 Subject: [PATCH 3/6] Add UsmNDArray_RemoveQueueRef cdef api --- dpnp/tensor/_usmarray.pyx | 6 ++++++ 1 file changed, 6 insertions(+) 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""" From 31f70c910b72ff78fb9bf3bd4356cccc313bc071 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 27 Aug 2026 04:01:32 -0700 Subject: [PATCH 4/6] Use UsmNDArray_RemoveQueueRef to free the queue copy --- dpnp/include/dpnp4pybind11.hpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/dpnp/include/dpnp4pybind11.hpp b/dpnp/include/dpnp4pybind11.hpp index 2cc55648bf4..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,12 +492,15 @@ 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 (DPCTLQueue_Copy, - // i.e. `new sycl::queue`); wrap it in a unique_ptr to avoid leaking a - // queue per call. + // UsmNDArray_GetQueueRef_ returns an owning copy; free it through + // RemoveQueueRef (dpctl's DPCTLQueue_Delete) DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - std::unique_ptr q_ptr{ - 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; } @@ -503,11 +509,15 @@ 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; wrap it in a - // unique_ptr to avoid leaking a queue per call. + // UsmNDArray_GetQueueRef_ returns an owning copy; free it through + // RemoveQueueRef (dpctl's DPCTLQueue_Delete) DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar); - std::unique_ptr q_ptr{ - 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->get_device(); } From 124a89122352c43bbd8f49f1551c05212f32350a Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 27 Aug 2026 04:01:48 -0700 Subject: [PATCH 5/6] Fix queue-ref leak in capi test via RemoveQueueRef --- dpnp/tests/tensor/test_usm_ndarray_capi.py | 11 +++++++++++ 1 file changed, 11 insertions(+) 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(): From b1c3e71cd23aca32ab79e2c60cf28164058f4eae Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 27 Aug 2026 04:02:10 -0700 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f956a0c5e..5699b7e8a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This release is compatible with NumPy 2.5. * Added `dpnp.tensor.broadcast_shapes` to align with the 2025.12 version of the Python array API [#3009](https://github.com/IntelPython/dpnp/pull/3009) * 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 `UsmNDArray_RemoveQueueRef` C API function to release a queue reference obtained from `UsmNDArray_GetQueueRef` [#3042](https://github.com/IntelPython/dpnp/pull/3042) ### Changed