Skip to content

Fix cuda.bindings: lock lazy init of _cyb_func_ptrs in driver _inspect_function_pointer - #3406

Open
lijinf2 wants to merge 4 commits into
NVIDIA:mainfrom
lijinf2:fix-3373-func-ptrs-lock
Open

lijinf2 wants to merge 4 commits into
NVIDIA:mainfrom
lijinf2:fix-3373-func-ptrs-lock

Conversation

@lijinf2

@lijinf2 lijinf2 commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #3373

reference cybind MR#: 619

Summary

_inspect_function_pointer lazily builds a module-level _cyb_func_ptrs dict with an unsynchronized "check None, then assign". Under free-threaded Python several threads can each build and publish their own dict, and the replaced dict can be freed while another thread reads it. This is the SystemError: ... dictobject.c: bad argument to internal function seen in test_private_function_pointer_inspector on Python 3.14t.

The fix uses double-checked locking with a dedicated _cyb_func_ptrs_lock: only the first initialization takes the lock, and later lookups are lock-free reads of the published dict.

Notes

  • The _internal/driver_{linux,windows}.pyx files are regenerated from the cybind change (generator-side fix), not hand-edited.

🤖 Generated with Claude Code

@copy-pr-bot

copy-pr-bot Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the cuda.bindings Everything related to the cuda.bindings module label Nov 7, 2026
@lijinf2 lijinf2 self-assigned this Nov 7, 2026
@lijinf2 lijinf2 changed the title Lock lazy init of _cyb_func_ptrs in driver _inspect_function_pointer Fix cuda.bindings bug: lock lazy init of _cyb_func_ptrs in driver _inspect_function_pointer Nov 7, 2026
@lijinf2 lijinf2 added the bug Something isn't working label Nov 7, 2026
@lijinf2 lijinf2 added this to the cuda.bindings next milestone Nov 7, 2026
@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved the reliability of CUDA driver bindings on Linux and Windows when multiple threads access function pointers at the same time. Function pointers are now initialized once and consistently retrieved from the shared cache, reducing the chance of inconsistent behavior in multithreaded applications.

The Linux and Windows drivers now use a lock to initialize the cached function-pointer dictionary once. Both generated-file headers add a prerelease API warning and update the content hash.

Changes: Driver cache synchronization

Layer / File(s) Summary
Lock function-pointer cache initialization
cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx, cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx
Both drivers separate dictionary construction from cache access. They use a lock and a second cache check before storing the dictionary. The generated-file headers add a prerelease API warning and update the content hash.

Assessment against linked issues

Objective Addressed Explanation
Prevent test_private_function_pointer_inspector from failing under free-threaded Python on Windows [#3373] ✅

Suggested reviewers: mdboom

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: Minimal · up to 45c27

The reported concurrent cache-initialization risk is addressed in both drivers. No actionable merge-blocking issue remains after normal checks.

Architecture risk: Low · up to 45c27

The change affects 1 system.

Changed systems: cuda_bindings_13

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — cuda_bindings_13 (service) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx: Added a prerelease-API warning and updated the generated-file content hash.
  • observed — Modified behavior in cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx: Added _cyb_func_ptrs_lock to synchronize initialization of the cached function-pointer dictionary.
  • observed — Modified behavior in cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx: _build_function_pointers returns the constructed pointer dictionary without assigning it to the cache. _inspect_function_pointers now initializes that cache once using a lock and a second None check; _inspect_function_pointer delegates lookup to the cached accessor instead of initializing the cache itself.
  • observed — Modified behavior in cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx: Adds the prerelease-API warning and updates the generated-file content hash.
- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between ab8ac53 and 45c27f2.

Files selected for processing: 2

  • cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx
  • cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

@lijinf2 lijinf2 changed the title Fix cuda.bindings bug: lock lazy init of _cyb_func_ptrs in driver _inspect_function_pointer Fix cuda.bindings: lock lazy init of _cyb_func_ptrs in driver _inspect_function_pointer Nov 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between db463bf and ab8ac53.

Files selected for processing: 2

  • cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx
  • cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

_cyb_func_ptrs = _inspect_function_pointers()
with _cyb_func_ptrs_lock:
if _cyb_func_ptrs is None:
_cyb_func_ptrs = _inspect_function_pointers()

@coderabbitai coderabbitai Bot Nov 7, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Stability & Availability · Heavy lift

Supported by static analysis

Script executed:

git diff 960be69abeb9b12ac91ce7604ee07cca803a401e ab8ac534c72cf441adb529951230336c0ea51f38 -- cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx
rg -n -F -e '_inspect_function_pointers' -e '_inspect_function_pointer(' -e '_cyb_func_ptrs' -- cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx cuda_bindings_13/tests cybind 2>/dev/null
sed -n '1,90p;3740,3830p' cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx | nl -ba

Repository: NVIDIA/cuda-python

Length of output: 13432


Script executed:

printf '%s\n' '--- inspector start/end ---'
nl -ba cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx | sed -n '2180,2235p;3775,3805p'
printf '%s\n' '--- all plural-inspector references ---'
rg -n -F --glob '!*.pyx' --glob '!*.pxd' -- '_inspect_function_pointers' . || test "$?" -eq 1
printf '%s\n' '--- generator and free-threading configuration ---'
rg -n -F -e 'freethreading_compatible' -e 'free-threading' -e '_cyb_func_ptrs_lock' -e 'def _inspect_function_pointers' -- cuda_bindings_13 cybind pyproject.toml 2>/dev/null || test "$?" -eq 1
printf '%s\n' '--- test binding ---'
nl -ba cuda_bindings_13/tests/test_cuda.py | sed -n '940,970p'

Repository: NVIDIA/cuda-python

Length of output: 8014


Script executed:

printf '%s\n' '--- cuda.core caller ---'
nl -ba cuda_core/cuda/core/_cpp/rt/py_driver_fns.cpp | sed -n '170,250p'
nl -ba cuda_core/cuda/core/_cpp/rt/driver_api.hpp | sed -n '165,205p'
printf '%s\n' '--- generator locations ---'
rg -n -F -e '_cyb_func_ptrs' -e '_inspect_function_pointers' -- cython_bindings cybind cuda_bindings_13 2>/dev/null | grep -v -E '/_internal/[^:]+\.pyx:' | sed -n '1,120p'
printf '%s\n' '--- build directives ---'
nl -ba cuda_bindings_13/build_hooks.py | sed -n '325,350p'

Repository: NVIDIA/cuda-python

Length of output: 7611


Script executed:

rg -n -i -e 'cython[>=<~ ]' -e 'cython_version' -- cuda_bindings_13/pyproject.toml cuda_bindings_13/build_hooks.py pyproject.toml 2>/dev/null || test "$?" -eq 1
rg --files | rg -i '(cybind|cython.binding|generator)' | sed -n '1,100p'

Repository: NVIDIA/cuda-python

Length of output: 452


Web query:

Cython free threading cdef global variables concurrent read write thread safety documentation

Result:

Sources consulted:
- cython/docs/src/userguide/source_files_and_compilation.rst at master · cython/cython · GitHub — https://github.com/cython/cython/blob/master/docs/src/userguide/source_files_and_compilation.rst
- Free threading — Cython 3.4.0a0 documentation — https://docs.cython.org/en/latest/src/userguide/freethreading.html
- cython/docs/src/userguide/freethreading.rst at master · cython/cython · GitHub — https://github.com/cython/cython/blob/master/docs/src/userguide/freethreading.rst
- …and 22 more

Web query:

Cython 3.2 free threading cdef global variable thread safety read write reference counting documentation

Result:

Sources consulted:
- cython/docs/src/userguide/freethreading.rst at master · cython/cython · GitHub — https://github.com/cython/cython/blob/master/docs/src/userguide/freethreading.rst
- Source Files and Compilation — Cython 3.3.0 documentation — https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html?highlight=cythonize
- Free threading — Cython 3.3.0 documentation — https://cython.readthedocs.io/en/latest/src/userguide/freethreading.html
- cython/docs/src/userguide/source_files_and_compilation.rst at master · cython/cython · GitHub — https://github.com/cython/cython/blob/master/docs/src/userguide/source_files_and_compilation.rst
- …and 29 more

Script executed:

python3 - <<'PY'
import importlib.metadata as metadata
import importlib.util
try:
    print("Cython version:", metadata.version("Cython"))
    spec = importlib.util.find_spec("Cython")
    print("Cython source root:", list(spec.submodule_search_locations) if spec else None)
except metadata.PackageNotFoundError:
    print("Cython distribution unavailable")
PY

Repository: NVIDIA/cuda-python

Length of output: 188


important: Synchronize both cache writers.

On free-threaded Python, a direct call to _inspect_function_pointers() can overlap the first _inspect_function_pointer() call and replace _cyb_func_ptrs without the lock. This can cause unsafe reference access or a crash.

Coordinate initialization in both entry points. Avoid acquiring the non-reentrant lock twice. Update the generator and regenerate both platform variants.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. This has been fixed. please review the fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Analysis chain

Script executed:

#!/bin/bash
set -u
echo '--- checked-out revision ---'
git rev-parse HEAD
echo '--- changes since reviewed commit ---'
git diff --stat ab8ac534c72cf441adb529951230336c0ea51f38 HEAD
echo '--- cache references in both variants ---'
rg -n -C 4 '(^cdef (dict|object) _cyb_func_ptrs|^cdef dict _build_function_pointers|^cpdef (dict )?_inspect_function_pointers|^cpdef _inspect_function_pointer|_cyb_func_ptrs =)' cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx
echo '--- generated-source diff since reviewed commit ---'
git diff ab8ac534c72cf441adb529951230336c0ea51f38 HEAD -- cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx cuda_bindings_13/cuda/bindings/_internal/driver_windows.pyx | head -180
echo '--- inspector tests and generator references ---'
rg -n -C 3 'test_private_function_pointer_inspector|_inspect_function_pointers|_cyb_func_ptrs_lock' cuda_bindings_13/tests cybind -g '!*.pyx' -g '!*.pxd' 2>/dev/null | head -160
echo '--- relevant policy files ---'
for f in AGENTS.md CLAUDE.md cuda_bindings_13/AGENTS.md cuda_bindings_13/CLAUDE.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n -i -C 3 'review|resolv|thread|generated|platform|cybind' "$f" || true
  fi
done
echo '--- cython dependency ---'
rg -n -i 'cython[[:space:]]*[><=~]|freethreading_compatible' cuda_bindings_13/pyproject.toml cuda_bindings_13/build_hooks.py pyproject.toml 2>/dev/null || true

Length of output: 24893


web_search executed:

For Cython 3 free-threaded Python, are cdef module-level Python object variables automatically protected for concurrent reads and writes? Does a lock held by writers alone make an unlocked reader safe? Find relevant Cython and CPython documentation.

Length of output: 30297


Script executed:

#!/bin/bash
set -u
echo '--- nested repository instructions ---'
fd -H -t f '^(AGENTS|CLAUDE)\.md$' cuda_bindings_13/cuda cuda_bindings_13/tests 2>/dev/null | while IFS= read -r f; do
  echo "--- $f ---"
  rg -n -i -C 3 'review|resolv|thread|generated|platform|cybind' "$f" || true
done
echo '--- installed Cython metadata ---'
(cd /tmp && python3 - <<'PY'
import importlib.metadata
import importlib.util
try:
    print("version:", importlib.metadata.version("Cython"))
    spec = importlib.util.find_spec("Cython")
    print("source:", list(spec.submodule_search_locations) if spec else None)
except importlib.metadata.PackageNotFoundError:
    print("Cython not installed")
PY
)

Length of output: 946


web_search executed:

In Cython 3.2.5 free-threaded Python, are reads and assignments to cdef module-level Python object variables automatically atomic or synchronized? Find documentation or Cython source evidence for concurrent read/write safety and memory publication.

Length of output: 30296


Script executed:

#!/bin/bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
python3 -m pip install --disable-pip-version-check --quiet --target "$tmp/packages" 'Cython==3.2.5'
cat > "$tmp/cache_example.pyx" <<'EOF'
# cython: freethreading_compatible=True
import threading
cdef dict cache = None
cdef object lock = threading.Lock()
cdef dict build():
    return {"key": 1}
cpdef dict inspect_cache():
    global cache
    if cache is None:
        with lock:
            if cache is None:
                cache = build()
    return cache
EOF
PYTHONPATH="$tmp/packages" python3 -m cython -3 -o "$tmp/cache_example.c" "$tmp/cache_example.pyx"
echo '--- generated cache reads and writes ---'
rg -n -C 3 '__pyx_v_13cache_example_cache|__pyx_f_13cache_example_inspect_cache' "$tmp/cache_example.c" | head -170

Length of output: 3882


@lijinf2 important: Both entry points now share one initializer, but the first read of _cyb_func_ptrs remains outside the lock. It can race with the assignment during initialization on free-threaded Python.

A standalone Cython 3.2.5 translation of this pattern emitted ordinary C pointer accesses. Please protect the read and assignment with the same lock, or use a safe acquire/release publication scheme.

Both platform variants have the same implementation. I could not inspect the generator source or run the repository tests. Please verify the generator change and run a concurrent first-call test for both entry points on Windows Python 3.14t.


Learnings used

Learnt from: CR
Repo: NVIDIA/cuda-python PR: 0
File: cuda_bindings_13/AGENTS.md:0-0
Timestamp: 2026-10-20T22:59:48.070Z
Learning: Applies to cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx : Source excerpt:
## Generated-source workflow

- **Platform split files**: keep `_linux.pyx` and `_windows.pyx` variants
  aligned when behavior should be equivalent.
Learnt from: CR
Repo: NVIDIA/cuda-python PR: 0
File: cuda_bindings_13/CLAUDE.md:0-0
Timestamp: 2026-10-31T04:59:49.354Z
Learning: Applies to cuda_bindings_13/cuda/bindings/_internal/driver_linux.pyx : Source excerpt:
## Generated-source workflow

- **Platform split files**: keep `_linux.pyx` and `_windows.pyx` variants
  aligned when behavior should be equivalent.

You are interacting with an AI system.

@lijinf2

lijinf2 commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test ab8ac53

@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

…_function_pointers() to a single build function having the fix
@lijinf2

lijinf2 commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 45c27f2

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cuda.bindings Everything related to the cuda.bindings module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_private_function_pointer_inspector fails under free-threaded Python

1 participant