Skip to content

Commit eb121f0

Browse files
committed
Cheaper and safe caching for py_capsule! and py_capsule_fn!
The `py_capsule!` and `py_capsule_fn!` macros generate `retrieve` functions that cache their results for subsequent calls. Prior to this commit, caching is done with a generated unsafe `static mut` item whose access is made thread-safe by a `std::sync::Once` on the side. However this synchronization is unnecessary since `retreive` takes a `Python<'_>` parameter that indicates that the GIL is held. This changes the cache to use safe `static` item instead, with `GILProtected` for synchronization-free thread-safety and `OnceCell` for interior mutability. As a bonus, this removes the need for cache-related `unsafe` code in generated `retrieve` functions. This adds a dependency to the `once_cell` crate, which can be removed when `OnceCell` becomes stable in the standard library: rust-lang/rust#74465 Alternatively `OnceCell` could be replaced with `UnsafeCell` plus some `unsafe` code, effectively inlining the core of the logic of `OnceCell`. `RefCell` might work too. Fixes #263
1 parent 0fcf769 commit eb121f0

File tree

3 files changed

+26
-23
lines changed

3 files changed

+26
-23
lines changed

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ appveyor = { repository = "dgrunwald/rust-cpython" }
3535
[dependencies]
3636
libc = "0.2"
3737
num-traits = "0.2"
38+
39+
# TODO: use `std::lazy::OnceCell` instead and when it’s stable
40+
# and this crate requires a minimum Rust version that has it stable:
41+
# https://github.com/rust-lang/rust/issues/74465
42+
once_cell = "1.8"
43+
3844
paste = "1"
3945
serde = { version = "1", features = ["derive"], optional = true }
4046

@@ -43,7 +49,7 @@ rustversion = "1.0"
4349
serde_bytes = { version = "0.11" }
4450
serde_cbor = { version = "0.11" }
4551

46-
# These features are both optional, but you must pick one to
52+
# These features are both optional, but you must pick one to
4753
# indicate which python ffi you are trying to bind to.
4854
[dependencies.python27-sys]
4955
optional = true
@@ -83,7 +89,7 @@ py-link-mode-default = [ "python3-sys/link-mode-default" ]
8389
py-link-mode-unresolved-static = [ "python3-sys/link-mode-unresolved-static" ]
8490

8591
# Optional features to support explicitly specifying python minor version.
86-
# If you don't care which minor version, just specify python3-sys as a
92+
# If you don't care which minor version, just specify python3-sys as a
8793
# feature.
8894
python-3-9 = ["python3-sys/python-3-9"]
8995
python-3-8 = ["python3-sys/python-3-8"]

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ pub mod _detail {
235235
handle_callback, py_fn_impl, AbortOnDrop, PyObjectCallbackConverter,
236236
PythonObjectCallbackConverter,
237237
};
238+
pub use once_cell::unsync::OnceCell;
238239
pub use paste;
239240
}
240241

src/objects/capsule.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -347,28 +347,27 @@ macro_rules! py_capsule {
347347
(from $($capsmod:ident).+ import $capsname:ident as $rustmod:ident for $ruststruct: ident ) => (
348348
mod $rustmod {
349349
use super::*;
350-
use std::sync::Once;
350+
use $crate::_detail::OnceCell;
351+
use $crate::GILProtected;
351352
use $crate::PyClone;
352353

353-
static mut CAPS_DATA: Option<$crate::PyResult<&$ruststruct>> = None;
354-
355-
static INIT: Once = Once::new();
354+
static CAPS_DATA: GILProtected<OnceCell<$crate::PyResult<&$ruststruct>>> =
355+
GILProtected::new(OnceCell::new());
356356

357357
pub type RawPyObject = $crate::_detail::ffi::PyObject;
358358

359359
pub unsafe fn retrieve<'a>(py: $crate::Python) -> $crate::PyResult<&'a $ruststruct> {
360-
INIT.call_once(|| {
360+
let ref_to_result = CAPS_DATA.get(py).get_or_init(|| {
361361
let caps_name =
362362
std::ffi::CStr::from_bytes_with_nul_unchecked(
363363
concat!($( stringify!($capsmod), "."),*,
364364
stringify!($capsname),
365365
"\0").as_bytes());
366-
CAPS_DATA = Some($crate::PyCapsule::import_data(py, caps_name));
366+
$crate::PyCapsule::import_data(py, caps_name)
367367
});
368-
match CAPS_DATA {
369-
Some(Ok(d)) => Ok(d),
370-
Some(Err(ref e)) => Err(e.clone_ref(py)),
371-
_ => panic!("Uninitialized"), // can't happen
368+
match ref_to_result {
369+
&Ok(ref x) => Ok(x),
370+
&Err(ref e) => Err(e.clone_ref(py))
372371
}
373372
}
374373
}
@@ -397,7 +396,7 @@ macro_rules! py_capsule {
397396
/// ```ignore
398397
/// mod $rustmod {
399398
/// pub type CapsuleFn = unsafe extern "C" (args) -> ret_type ;
400-
/// pub unsafe fn retrieve<'a>(py: Python) -> PyResult<CapsuleFn) { ... }
399+
/// pub unsafe fn retrieve<'a>(py: Python) -> PyResult<CapsuleFn> { ... }
401400
/// }
402401
/// ```
403402
/// - a `RawPyObject` type suitable for signatures that involve Python C objects;
@@ -482,15 +481,15 @@ macro_rules! py_capsule_fn {
482481
(from $($capsmod:ident).+ import $capsname:ident as $rustmod:ident signature $( $sig: tt)* ) => (
483482
mod $rustmod {
484483
use super::*;
485-
use std::sync::Once;
484+
use $crate::_detail::OnceCell;
485+
use $crate::GILProtected;
486486
use $crate::PyClone;
487487

488488
pub type CapsuleFn = unsafe extern "C" fn $( $sig )*;
489489
pub type RawPyObject = $crate::_detail::ffi::PyObject;
490490

491-
static mut CAPS_FN: Option<$crate::PyResult<CapsuleFn>> = None;
492-
493-
static INIT: Once = Once::new();
491+
static CAPS_FN: GILProtected<OnceCell<$crate::PyResult<CapsuleFn>>> =
492+
GILProtected::new(OnceCell::new());
494493

495494
fn import(py: $crate::Python) -> $crate::PyResult<CapsuleFn> {
496495
unsafe {
@@ -504,12 +503,9 @@ macro_rules! py_capsule_fn {
504503
}
505504

506505
pub fn retrieve(py: $crate::Python) -> $crate::PyResult<CapsuleFn> {
507-
unsafe {
508-
INIT.call_once(|| { CAPS_FN = Some(import(py)) });
509-
match CAPS_FN.as_ref().unwrap() {
510-
&Ok(f) => Ok(f),
511-
&Err(ref e) => Err(e.clone_ref(py)),
512-
}
506+
match CAPS_FN.get(py).get_or_init(|| import(py)) {
507+
&Ok(f) => Ok(f),
508+
&Err(ref e) => Err(e.clone_ref(py)),
513509
}
514510
}
515511
}

0 commit comments

Comments
 (0)