Skip to content

Commit 9c1783d

Browse files
chore(native): declare PyContextVar_* locally for limited-API builds
pyo3-ffi gates its entire `context` module behind `not(Py_LIMITED_API)` (pyo3-ffi 0.28.3 `src/lib.rs` lines 436 and 503), so `ffi::PyContextVar_New`, `ffi::PyContextVar_Get` and `ffi::PyContextVar_Set` disappear whenever the extension is built against the stable ABI. On CPython 3.15 that happens via `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`, since pyo3-build-config 0.28.3 caps `ABI3_MAX_MINOR` at 14. The build then fails with three E0425 "cannot find function ... in module `ffi`" errors on this file. Declare the three stable C API entry points locally so the module compiles in both configurations. Context variables date to Python 3.7, so the symbols are present on every interpreter ddtrace supports; verified to compile clean on 3.9, 3.13 and 3.15 with and without the stable-ABI path. Same class of fix as #18429, which closed the sibling `PyFrame_GetBack` gap for the crashtracker. Extracted from fc09033 on #19833, which bundled it with three unrelated formatting fixes. The code is unchanged from that commit; only the explanatory comment above the declarations was reworded. Co-authored-by: Vlad Scherbich <vlad.scherbich@datadoghq.com>
1 parent f668305 commit 9c1783d

1 file changed

Lines changed: 21 additions & 6 deletions

File tree

src/native/contextvar.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@ use pyo3::ffi;
22
use pyo3::prelude::*;
33
use std::ffi::CString;
44

5+
// PyContextVar_New/_Get/_Set are absent from pyo3-ffi's stable/limited-API bindings
6+
// (Py_LIMITED_API), which is activated for Python 3.15+ via PYO3_USE_ABI3_FORWARD_COMPATIBILITY.
7+
// Declare the stable C API locally so this module builds either way. Context variables were
8+
// introduced in Python 3.7, so these symbols are available on every supported interpreter.
9+
unsafe extern "C" {
10+
fn PyContextVar_New(
11+
name: *const std::os::raw::c_char,
12+
def: *mut ffi::PyObject,
13+
) -> *mut ffi::PyObject;
14+
fn PyContextVar_Get(
15+
var: *mut ffi::PyObject,
16+
default_value: *mut ffi::PyObject,
17+
value: *mut *mut ffi::PyObject,
18+
) -> std::os::raw::c_int;
19+
fn PyContextVar_Set(var: *mut ffi::PyObject, value: *mut ffi::PyObject) -> *mut ffi::PyObject;
20+
}
21+
522
/// Create a new `contextvars.ContextVar` via the C API (`PyContextVar_New`)
623
/// with `default` as its default value. Avoids importing the `contextvars`
724
/// module from Rust.
@@ -13,9 +30,7 @@ pub fn contextvar_new<'py>(
1330
let c_name = CString::new(name).expect("contextvar name must not contain NUL bytes");
1431
// SAFETY: `c_name` is a valid NUL-terminated string for the duration of the call;
1532
// `default.as_ptr()` is a valid borrowed reference (PyContextVar_New takes its own ref).
16-
unsafe {
17-
Bound::from_owned_ptr_or_err(py, ffi::PyContextVar_New(c_name.as_ptr(), default.as_ptr()))
18-
}
33+
unsafe { Bound::from_owned_ptr_or_err(py, PyContextVar_New(c_name.as_ptr(), default.as_ptr())) }
1934
}
2035

2136
/// Read the current value of `var` via `PyContextVar_Get`.
@@ -28,7 +43,7 @@ pub fn contextvar_get<'py>(
2843
) -> PyResult<Bound<'py, PyAny>> {
2944
let mut value: *mut ffi::PyObject = std::ptr::null_mut();
3045
// SAFETY: `var` is a valid ContextVar object; `value` is a valid out-pointer.
31-
let rc = unsafe { ffi::PyContextVar_Get(var.as_ptr(), std::ptr::null_mut(), &mut value) };
46+
let rc = unsafe { PyContextVar_Get(var.as_ptr(), std::ptr::null_mut(), &mut value) };
3247
if rc < 0 {
3348
return Err(PyErr::take(py).unwrap_or_else(|| {
3449
pyo3::exceptions::PyRuntimeError::new_err("PyContextVar_Get failed")
@@ -80,7 +95,7 @@ pub fn safe_contextvar_set(
8095
}));
8196
}
8297

83-
let token = ffi::PyContextVar_Set(var.as_ptr(), value.as_ptr());
98+
let token = PyContextVar_Set(var.as_ptr(), value.as_ptr());
8499

85100
// Capture any error before the decrefs below can perturb interpreter state.
86101
let err = if token.is_null() {
@@ -113,7 +128,7 @@ pub fn safe_contextvar_set(
113128
) -> PyResult<()> {
114129
// SAFETY: the GIL (`py`) is held for the duration of the call.
115130
unsafe {
116-
let token = ffi::PyContextVar_Set(var.as_ptr(), value.as_ptr());
131+
let token = PyContextVar_Set(var.as_ptr(), value.as_ptr());
117132
if token.is_null() {
118133
return Err(PyErr::take(py).unwrap_or_else(|| {
119134
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("PyContextVar_Set failed")

0 commit comments

Comments
 (0)