Skip to content

Commit 9f2c8f2

Browse files
test: poll for kernel-arg Buffer release to handle async cleanup
The freeing assertion at the end of test_kernel_args_buffer_lifetime failed on free-threaded Python (py3.14t) because cuGraphExecDestroy releases its user-object references via an asynchronous DPC, and free- threaded CPython's deferred ref counting can need an extra GC pass to settle. Poll the weakref with a bounded timeout and per-iteration GC instead of asserting eagerly. Co-authored-by: Cursor <[email protected]>
1 parent a9271aa commit 9f2c8f2

1 file changed

Lines changed: 25 additions & 2 deletions

File tree

‎cuda_core/tests/graph/test_graph_definition_lifetime.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,31 @@
55

66
import ctypes
77
import gc
8+
import time
89
import weakref
910

1011
import pytest
1112
from helpers.graph_kernels import compile_common_kernels
1213
from helpers.misc import try_create_condition
1314

15+
16+
def _wait_until(predicate, timeout=2.0, interval=0.01):
17+
"""Poll predicate() until True or timeout, driving gc each iteration.
18+
19+
Used for assertions about resource cleanup that may be delayed by CUDA's
20+
asynchronous user-object destructor pump (DPC) or, on free-threaded
21+
Python, by deferred reference-count processing. A bounded poll keeps the
22+
test correct without depending on undocumented driver timing guarantees.
23+
"""
24+
deadline = time.monotonic() + timeout
25+
while time.monotonic() < deadline:
26+
gc.collect()
27+
if predicate():
28+
return
29+
time.sleep(interval)
30+
raise AssertionError(f"condition not satisfied within {timeout}s")
31+
32+
1433
from cuda.core import Device, DeviceMemoryResource, EventOptions, Kernel, LaunchConfig
1534
from cuda.core.graph import (
1635
ChildGraphNode,
@@ -502,6 +521,11 @@ def test_kernel_args_buffer_lifetime(init_cuda):
502521
Without the user-object attachment, the ParamHolder is destroyed when the
503522
kernel node is added, the Buffer is GC'd, and the graph is left with a
504523
stale device pointer.
524+
525+
The final freeing assertion uses a bounded poll because CUgraphExec
526+
releases its user-object references via an asynchronous DPC, and on
527+
free-threaded Python the resulting Py_DECREF chain may need an extra
528+
GC pass to settle.
505529
"""
506530
from cuda.core._utils.cuda_utils import driver, handle_return
507531

@@ -531,8 +555,7 @@ def test_kernel_args_buffer_lifetime(init_cuda):
531555
assert out[0] == 1
532556

533557
del g
534-
gc.collect()
535-
assert buf_weak() is None # graph released, Buffer freed
558+
_wait_until(lambda: buf_weak() is None)
536559

537560

538561
def test_kernel_args_survive_graph_clone(init_cuda):

0 commit comments

Comments
 (0)