Skip to content

[UR][OpenCL] Break reference cycle between in-order queue and last event - #23076

Open
uditagarwal97 wants to merge 1 commit into
intel:syclfrom
uditagarwal97:private/udit/ur-opencl-queue-event-cycle
Open

[UR][OpenCL] Break reference cycle between in-order queue and last event#23076
uditagarwal97 wants to merge 1 commit into
intel:syclfrom
uditagarwal97:private/udit/ur-opencl-queue-event-cycle

Conversation

@uditagarwal97

@uditagarwal97 uditagarwal97 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem

ur_queue_handle_t_::storeLastEvent() retained the ur_event_handle_t_ it stores (queue.hpp:71), and ur_event_handle_t_'s constructor retains its Queue (event.hpp:31). For an in-order queue that is an unbreakable cycle:

  • queue -> LastEvent (strong, via urEventRetain)
  • event -> Queue (strong, via urQueueRetain)

The queue's reference count never reaches zero, so ~ur_queue_handle_t_ never runs and its urContextRelease / urDeviceRelease never fire. The queue, its context, its device and the event are all leaked for the lifetime of the process.

Proposed Fix

Store the cl_event instead of the UR event, retained with clRetainEvent and released in the destructor. The queue only ever needed that event to answer UR_QUEUE_INFO_EMPTY, and a cl_event holds no reference back to the UR queue, so the cycle cannot form.

The UR_QUEUE_INFO_EMPTY query now calls clGetEventInfo(CL_EVENT_COMMAND_EXECUTION_STATUS) directly, which is the identical call the neighbouring branch for a queue with no stored event already made.

How it was found

An ASan+UBSan build of the runtime running sycl/test-e2e/Basic on opencl:cpu. LeakSanitizer reported it as four indirect leaks with no direct leak at all, which is the signature of a reference cycle (no chunk is unreferenced, so nothing qualifies as the root):

Indirect leak of 72 byte(s) in 1 object(s) allocated from:
    #2 ur::opencl::urQueueCreate(...) queue.cpp:165
Indirect leak of 56 byte(s) in 1 object(s) allocated from:
    #2 ur::opencl::urContextCreate(...) context.cpp:84
Indirect leak of 40 byte(s) in 1 object(s) allocated from:
    #2 ur::opencl::createUREvent(...) event.hpp:74
Indirect leak of 8 byte(s) in 1 object(s)
SUMMARY: AddressSanitizer: 176 byte(s) leaked in 4 allocation(s).

The proposed fix resolves all the leaks.


Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

ur_queue_handle_t_::storeLastEvent() retained the ur_event_handle_t_ it
stores, and ur_event_handle_t_'s constructor retains its queue. For an
in-order queue that is a cycle: the queue's reference count never reaches
zero, so ~ur_queue_handle_t_ never runs and the queue, its context, its
device and the event are leaked for the lifetime of the process.
storeLastEvent() is called on every enqueue to an in-order queue, so this
affects any sycl::queue created with the in_order property.

Store the cl_event instead of the UR event. The queue only needs it to
answer UR_QUEUE_INFO_EMPTY, and a cl_event holds no reference back to the
UR queue, so the cycle cannot form. That query now calls clGetEventInfo()
directly, which is already what the sibling branch for a queue with no
stored event does.

Found with an ASan+UBSan build of the runtime running the
sycl/test-e2e/Basic tests on opencl:cpu. LeakSanitizer reported the cycle
as four indirect leaks with no direct leak, which is the signature of a
reference cycle. Covered by Basic/khr_flush.cpp and
Basic/in_order_queue_status_{khr,ext_oneapi}_empty.cpp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The ownership fix lacks a lifecycle regression test capable of detecting the original reference cycle.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Breaks the OpenCL in-order queue/event reference cycle while preserving empty-queue tracking.

Changes:

  • Stores and manages a retained native cl_event.
  • Queries native event completion status directly.
  • Passes native events into queue tracking.
File summaries
File Description
queue.hpp Replaces strong UR-event ownership with native-event ownership.
queue.cpp Queries native event execution status.
event.hpp Records the native event after enqueue.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

auto UREvent =
std::make_unique<ur_event_handle_t_>(Event, cast(Context), UrQueue);
UR_RETURN_ON_FAILURE(UrQueue->storeLastEvent(cast(UREvent.get())));
UR_RETURN_ON_FAILURE(UrQueue->storeLastEvent(Event));
@uditagarwal97
uditagarwal97 marked this pull request as ready for review September 1, 2026 23:24
@uditagarwal97
uditagarwal97 requested a review from a team as a code owner September 1, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants