@@ -1291,33 +1291,32 @@ def test_cudart_eglFrame():
12911291 assert int (frame .frame .pPitch [2 ].ysize ) == 0
12921292
12931293
1294- def cudart_func_stream_callback (use_host_api ):
1294+ @pytest .mark .parametrize ("use_host_api" , [False , True ], ids = ["stream_callback" , "host_function" ])
1295+ @pytest .mark .agent_authored (model = "gpt-6" )
1296+ def test_cudart_func_callback (use_host_api ):
12951297 class testStruct (ctypes .Structure ):
12961298 _fields_ = [
12971299 ("a" , ctypes .c_int ),
12981300 ("b" , ctypes .c_int ),
12991301 ("c" , ctypes .c_int ),
13001302 ]
13011303
1304+ calls = []
1305+ stream_results = []
1306+
13021307 def task_callback_host (userData ):
13031308 data = testStruct .from_address (userData )
1304- assert data .a == 1
1305- assert data .b == 2
1306- assert data .c == 3
1307- return 0
1309+ calls .append ((userData , data .a , data .b , data .c ))
13081310
13091311 def task_callback_stream (stream , status , userData ):
1310- data = testStruct .from_address (userData )
1311- assert data .a == 1
1312- assert data .b == 2
1313- assert data .c == 3
1314- return 0
1312+ stream_results .append ((stream , status ))
1313+ task_callback_host (userData )
13151314
13161315 if use_host_api :
1317- callback_type = ctypes .PYFUNCTYPE ( ctypes . c_int , ctypes .c_void_p )
1316+ callback_type = ctypes .CFUNCTYPE ( None , ctypes .c_void_p )
13181317 target_task = task_callback_host
13191318 else :
1320- callback_type = ctypes .PYFUNCTYPE ( ctypes . c_int , ctypes .c_void_p , ctypes .c_int , ctypes .c_void_p )
1319+ callback_type = ctypes .CFUNCTYPE ( None , ctypes .c_void_p , ctypes .c_int , ctypes .c_void_p )
13211320 target_task = task_callback_stream
13221321
13231322 # Construct ctype data
@@ -1333,19 +1332,64 @@ def task_callback_stream(stream, status, userData):
13331332 # Run
13341333 err , stream = cudart .cudaStreamCreate ()
13351334 assertSuccess (err )
1336- if use_host_api :
1337- (err ,) = cudart .cudaLaunchHostFunc (stream , callback , ctypes .addressof (c_data ))
1335+ try :
1336+ if use_host_api :
1337+ (err ,) = cudart .cudaLaunchHostFunc (stream , callback , ctypes .addressof (c_data ))
1338+ else :
1339+ (err ,) = cudart .cudaStreamAddCallback (stream , callback , ctypes .addressof (c_data ), 0 )
13381340 assertSuccess (err )
1339- else :
1340- (err ,) = cudart .cudaStreamAddCallback (stream , callback , ctypes .addressof (c_data ), 0 )
1341+ (err ,) = cudart .cudaStreamSynchronize (stream )
13411342 assertSuccess (err )
1342- (err ,) = cudart .cudaDeviceSynchronize ()
1343- assertSuccess (err )
13441343
1344+ # ctypes swallows callback exceptions, so assert on the test thread.
1345+ assert calls == [(ctypes .addressof (c_data ), 1 , 2 , 3 )]
1346+ if not use_host_api :
1347+ assert stream_results == [(int (stream ), int (cudart .cudaError_t .cudaSuccess ))]
1348+ finally :
1349+ (err ,) = cudart .cudaStreamDestroy (stream )
1350+ assertSuccess (err )
1351+
1352+
1353+ @pytest .mark .agent_authored (model = "claude-fable-5-1" )
1354+ def test_cudart_host_func_graph_replay ():
1355+ # A host function captured into a graph runs once per graph launch. The
1356+ # bindings must hand the function pointer and the user data to CUDA
1357+ # unchanged so that every launch reaches the same function (issue #790).
1358+ counter = ctypes .c_int (0 )
13451359
1346- def test_cudart_func_callback ():
1347- cudart_func_stream_callback (use_host_api = False )
1348- cudart_func_stream_callback (use_host_api = True )
1360+ @ctypes .CFUNCTYPE (None , ctypes .c_void_p )
1361+ def host_fn (userData ):
1362+ ctypes .c_int .from_address (userData ).value += 1
1363+
1364+ callback = cudart .cudaHostFn_t (_ptr = ctypes .addressof (host_fn ))
1365+ err , stream = cudart .cudaStreamCreate ()
1366+ assertSuccess (err )
1367+ try :
1368+ (err ,) = cudart .cudaStreamBeginCapture (stream , cudart .cudaStreamCaptureMode .cudaStreamCaptureModeGlobal )
1369+ assertSuccess (err )
1370+ (err ,) = cudart .cudaLaunchHostFunc (stream , callback , ctypes .addressof (counter ))
1371+ assertSuccess (err )
1372+ err , graph = cudart .cudaStreamEndCapture (stream )
1373+ assertSuccess (err )
1374+ try :
1375+ err , graphExec = cudart .cudaGraphInstantiate (graph , 0 )
1376+ assertSuccess (err )
1377+ try :
1378+ for launch in range (1 , 6 ):
1379+ (err ,) = cudart .cudaGraphLaunch (graphExec , stream )
1380+ assertSuccess (err )
1381+ (err ,) = cudart .cudaStreamSynchronize (stream )
1382+ assertSuccess (err )
1383+ assert counter .value == launch
1384+ finally :
1385+ (err ,) = cudart .cudaGraphExecDestroy (graphExec )
1386+ assertSuccess (err )
1387+ finally :
1388+ (err ,) = cudart .cudaGraphDestroy (graph )
1389+ assertSuccess (err )
1390+ finally :
1391+ (err ,) = cudart .cudaStreamDestroy (stream )
1392+ assertSuccess (err )
13491393
13501394
13511395@pytest .mark .skipif (
0 commit comments