diff --git a/cuda_bindings/docs/source/release/12.X.Y-notes.rst b/cuda_bindings/docs/source/release/12.X.Y-notes.rst index f5e5d00a172..4d37acb7a2d 100644 --- a/cuda_bindings/docs/source/release/12.X.Y-notes.rst +++ b/cuda_bindings/docs/source/release/12.X.Y-notes.rst @@ -33,6 +33,8 @@ Highlights Bug fixes --------- +* Fixed a crash on free-threaded Python when callbacks (for example ``cuLogsRegisterCallback``) are registered or + unregistered from multiple threads at the same time. Access to the internal callback registry is now synchronized. Miscellaneous ------------- diff --git a/cuda_bindings/tests/test_callback_threads.py b/cuda_bindings/tests/test_callback_threads.py new file mode 100644 index 00000000000..1c0f44f2c8a --- /dev/null +++ b/cuda_bindings/tests/test_callback_threads.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import threading + +import pytest + +from cuda.bindings import driver + +NUM_THREADS = 16 +NUM_ITERATIONS = 500 + + +@pytest.mark.agent_authored(model="claude-sonnet-5-5") +def test_async_notification_register_unregister_from_many_threads(): + (err,) = driver.cuInit(0) + assert err == driver.CUresult.CUDA_SUCCESS + err, device = driver.cuDeviceGet(0) + assert err == driver.CUresult.CUDA_SUCCESS + + barrier = threading.Barrier(NUM_THREADS) + failures = [] + + def worker(): + try: + barrier.wait() + for _ in range(NUM_ITERATIONS): + err, handle = driver.cuDeviceRegisterAsyncNotification(device, 0, 0) + assert err == driver.CUresult.CUDA_SUCCESS + (err,) = driver.cuDeviceUnregisterAsyncNotification(device, handle) + assert err == driver.CUresult.CUDA_SUCCESS + except BaseException as e: + failures.append(e) + + threads = [threading.Thread(target=worker) for _ in range(NUM_THREADS)] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not failures, failures