@@ -611,48 +611,97 @@ def test_user_object_cleanup_is_coalesced_on_python_thread(init_cuda):
611611
612612
613613@pytest .mark .agent_authored (model = "gpt-5.6" )
614- def test_pending_call_queue_saturation_preserves_cleanup (init_cuda ):
614+ def test_pending_call_queue_saturation_preserves_cleanup (tmp_path ):
615615 """A full CPython queue neither strands nor mis-threads cleanup."""
616- pending_callback_type = ctypes .CFUNCTYPE (ctypes .c_int , ctypes .c_void_p )
617- add_pending_call = ctypes .pythonapi .Py_AddPendingCall
618- add_pending_call .argtypes = [pending_callback_type , ctypes .c_void_p ]
619- add_pending_call .restype = ctypes .c_int
616+ code = f"timeout = { _FINALIZE_TIMEOUT !r} \n " + textwrap .dedent (
617+ """
618+ import ctypes
619+ import gc
620+ import threading
621+ import time
622+
623+ from cuda.core import Device
624+ from cuda.core.graph import GraphDefinition
620625
621- @ pending_callback_type
622- def noop_pending_call ( _ ):
623- return 0
626+ class ThreadRecordingCallback:
627+ def __init__(self, finalized_threads ):
628+ self.finalized_threads = finalized_threads
624629
625- finalized_threads = []
626- main_thread = threading .get_ident ()
627- first_callback = _ThreadRecordingCallback (finalized_threads )
628- first_graph = GraphDefinition ()
629- first_graph .callback (first_callback )
630- graph_holder = [first_graph ]
631- worker_done = threading .Event ()
632- queue_was_full = []
630+ def __call__(self):
631+ pass
632+
633+ def __del__(self):
634+ self.finalized_threads.append(threading.get_ident())
635+
636+ def wait_until(predicate):
637+ deadline = time.monotonic() + timeout
638+ while True:
639+ gc.collect()
640+ if predicate():
641+ return
642+ if time.monotonic() >= deadline:
643+ break
644+ time.sleep(0)
645+ time.sleep(0.02)
646+ raise AssertionError(f"condition not satisfied within {timeout}s")
647+
648+ Device(0).set_current()
649+
650+ pending_callback_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p)
651+ add_pending_call = ctypes.pythonapi.Py_AddPendingCall
652+ add_pending_call.argtypes = [pending_callback_type, ctypes.c_void_p]
653+ add_pending_call.restype = ctypes.c_int
654+ make_pending_calls = ctypes.pythonapi.Py_MakePendingCalls
655+ make_pending_calls.argtypes = []
656+ make_pending_calls.restype = ctypes.c_int
657+
658+ @pending_callback_type
659+ def noop_pending_call(_):
660+ return 0
661+
662+ finalized_threads = []
663+ main_thread = threading.get_ident()
664+ first_callback = ThreadRecordingCallback(finalized_threads)
665+ first_graph = GraphDefinition()
666+ first_graph.callback(first_callback)
667+ graph_holder = [first_graph]
668+ worker_done = threading.Event()
669+ queue_was_full = []
670+
671+ del first_callback, first_graph
672+
673+ def fill_queue_and_destroy():
674+ while add_pending_call(noop_pending_call, None) == 0:
675+ pass
676+ queue_was_full.append(True)
677+ graph_holder.clear()
678+ worker_done.set()
633679
634- del first_callback , first_graph
680+ worker = threading.Thread(target=fill_queue_and_destroy)
681+ worker.start()
682+ assert worker_done.wait(timeout=5)
683+ worker.join()
684+ assert queue_was_full == [True]
635685
636- def fill_queue_and_destroy ():
637- while add_pending_call (noop_pending_call , None ) == 0 :
638- pass
639- queue_was_full .append (True )
640- graph_holder .clear ()
641- worker_done .set ()
642-
643- worker = threading .Thread (target = fill_queue_and_destroy )
644- worker .start ()
645- assert worker_done .wait (timeout = 5 )
646- worker .join ()
647- assert queue_was_full == [True ]
648-
649- # A later safe cuda-core close retries after the main thread has had an
650- # opportunity to drain the foreign pending calls.
651- retry_builder = Device ().create_graph_builder ()
652- retry_builder .close ()
653-
654- _wait_until (lambda : len (finalized_threads ) == 1 )
655- assert set (finalized_threads ) == {main_thread }
686+ # Free space before the cuda-core entry that retries cleanup scheduling.
687+ assert make_pending_calls() == 0
688+
689+ retry_builder = Device().create_graph_builder()
690+ retry_builder.close()
691+
692+ wait_until(lambda: len(finalized_threads) == 1)
693+ assert set(finalized_threads) == {main_thread}
694+ """
695+ )
696+ result = subprocess .run ( # noqa: S603 - controlled interpreter probe
697+ [sys .executable , "-c" , code ],
698+ capture_output = True ,
699+ text = True ,
700+ timeout = 60 ,
701+ # Isolate the process-global pending-call queue from parallel tests.
702+ cwd = tmp_path ,
703+ )
704+ assert result .returncode == 0 , result .stderr
656705
657706
658707@pytest .mark .agent_authored (model = "gpt-5.6" )
0 commit comments