Skip to content

Commit 9d4a410

Browse files
committed
embedding: use Py_SHARED cfg flag
1 parent bcaab65 commit 9d4a410

File tree

6 files changed

+101
-67
lines changed

6 files changed

+101
-67
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,27 +85,28 @@ jobs:
8585
run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV
8686

8787
- name: Build docs
88-
run: cargo doc --features "num-bigint num-complex hashbrown" --verbose --target ${{ matrix.platform.rust-target }}
88+
run: cargo doc --no-default-features --features "macros num-bigint num-complex hashbrown" --verbose --target ${{ matrix.platform.rust-target }}
8989

90-
- name: Build without default features
90+
- name: Build (no features)
9191
run: cargo build --no-default-features --verbose --target ${{ matrix.platform.rust-target }}
9292

93-
- name: Build with default features
94-
run: cargo build --features "num-bigint num-complex hashbrown" --verbose --target ${{ matrix.platform.rust-target }}
93+
- name: Build (all additive features)
94+
run: cargo build --no-default-features --features "macros num-bigint num-complex hashbrown" --verbose --target ${{ matrix.platform.rust-target }}
9595

9696
# Run tests (except on PyPy, because no embedding API).
9797
- if: matrix.python-version != 'pypy-3.6'
9898
name: Test
99-
run: cargo test --features "num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
99+
run: cargo test --no-default-features --features "macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
100+
100101
# Run tests again, but in abi3 mode
101102
- if: matrix.python-version != 'pypy-3.6'
102103
name: Test (abi3)
103-
run: cargo test --no-default-features --features "abi3,macros" --target ${{ matrix.platform.rust-target }}
104+
run: cargo test --no-default-features --features "abi3 macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
104105

105106
# Run tests again, for abi3-py36 (the minimal Python version)
106107
- if: (matrix.python-version != 'pypy-3.6') && (matrix.python-version != '3.6')
107108
name: Test (abi3-py36)
108-
run: cargo test --no-default-features --features "abi3-py36,macros" --target ${{ matrix.platform.rust-target }}
109+
run: cargo test --no-default-features --features "abi3-py36 macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
109110

110111
- name: Test proc-macro code
111112
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml --target ${{ matrix.platform.rust-target }}

Cargo.toml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ trybuild = "1.0.23"
3434
rustversion = "1.0"
3535
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
3636
# features needed to run the PyO3 test suite
37-
pyo3 = { path = ".", default-features = false, features = ["macros", "embedding", "auto-initialize"] }
37+
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
3838

3939
[features]
40-
default = ["macros", "embedding", "auto-initialize"]
40+
default = ["macros", "auto-initialize"]
4141

4242
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
4343
macros = ["pyo3-macros", "ctor", "indoc", "inventory", "paste", "unindent"]
@@ -56,12 +56,9 @@ abi3-py37 = ["abi3-py38"]
5656
abi3-py38 = ["abi3-py39"]
5757
abi3-py39 = ["abi3"]
5858

59-
# Enables embedding a Python interpreter inside Rust programs.
60-
embedding = []
61-
6259
# Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the
6360
# Python interpreter if needed.
64-
auto-initialize = ["embedding"]
61+
auto-initialize = []
6562

6663
# Optimizes PyObject to Vec conversion and so on.
6764
nightly = []

build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
749749
}
750750
}
751751

752+
if interpreter_config.shared {
753+
println!("cargo:rustc-cfg=Py_SHARED");
754+
}
755+
752756
if interpreter_config.version.implementation == PythonInterpreterKind::PyPy {
753757
println!("cargo:rustc-cfg=PyPy");
754758
};

src/gil.rs

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,20 @@ pub(crate) fn gil_is_acquired() -> bool {
4545
/// If both the Python interpreter and Python threading are already initialized,
4646
/// this function has no effect.
4747
///
48-
/// # Features
48+
/// # Availability
4949
///
50-
/// This function is only available with the `embedding` feature.
50+
/// This function is only available when linking against Python distributions that contain a
51+
/// shared library.
52+
///
53+
/// This function is not available on PyPy.
5154
///
5255
/// # Panic
5356
/// If the Python interpreter is initialized but Python threading is not,
5457
/// a panic occurs.
5558
/// It is not possible to safely access the Python runtime unless the main
5659
/// thread (the thread which originally initialized Python) also initializes
5760
/// threading.
58-
#[cfg(feature = "embedding")]
61+
#[cfg(all(Py_SHARED, not(PyPy)))]
5962
pub fn prepare_freethreaded_python() {
6063
// Protect against race conditions when Python is not yet initialized
6164
// and multiple threads concurrently call 'prepare_freethreaded_python()'.
@@ -67,42 +70,38 @@ pub fn prepare_freethreaded_python() {
6770
// as we can't make the existing Python main thread acquire the GIL.
6871
assert_ne!(ffi::PyEval_ThreadsInitialized(), 0);
6972
} else {
70-
// TODO remove this cfg once build.rs rejects embedding feature misuse.
71-
#[cfg(not(PyPy))]
72-
{
73-
// Initialize Python.
74-
// We use Py_InitializeEx() with initsigs=0 to disable Python signal handling.
75-
// Signal handling depends on the notion of a 'main thread', which doesn't exist in this case.
76-
// Note that the 'main thread' notion in Python isn't documented properly;
77-
// and running Python without one is not officially supported.
78-
79-
ffi::Py_InitializeEx(0);
80-
81-
// Make sure Py_Finalize will be called before exiting.
82-
extern "C" fn finalize() {
83-
unsafe {
84-
if ffi::Py_IsInitialized() != 0 {
85-
ffi::PyGILState_Ensure();
86-
ffi::Py_Finalize();
87-
}
88-
}
89-
}
90-
libc::atexit(finalize);
73+
// Initialize Python.
74+
// We use Py_InitializeEx() with initsigs=0 to disable Python signal handling.
75+
// Signal handling depends on the notion of a 'main thread', which doesn't exist in this case.
76+
// Note that the 'main thread' notion in Python isn't documented properly;
77+
// and running Python without one is not officially supported.
9178

92-
// > Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have
93-
// > to call it yourself anymore.
94-
#[cfg(not(Py_3_7))]
95-
if ffi::PyEval_ThreadsInitialized() == 0 {
96-
ffi::PyEval_InitThreads();
79+
ffi::Py_InitializeEx(0);
80+
81+
// Make sure Py_Finalize will be called before exiting.
82+
extern "C" fn finalize() {
83+
unsafe {
84+
if ffi::Py_IsInitialized() != 0 {
85+
ffi::PyGILState_Ensure();
86+
ffi::Py_Finalize();
87+
}
9788
}
89+
}
90+
libc::atexit(finalize);
9891

99-
// Py_InitializeEx() will acquire the GIL, but we don't want to hold it at this point
100-
// (it's not acquired in the other code paths)
101-
// So immediately release the GIL:
102-
let _thread_state = ffi::PyEval_SaveThread();
103-
// Note that the PyThreadState returned by PyEval_SaveThread is also held in TLS by the Python runtime,
104-
// and will be restored by PyGILState_Ensure.
92+
// > Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have
93+
// > to call it yourself anymore.
94+
#[cfg(not(Py_3_7))]
95+
if ffi::PyEval_ThreadsInitialized() == 0 {
96+
ffi::PyEval_InitThreads();
10597
}
98+
99+
// Py_InitializeEx() will acquire the GIL, but we don't want to hold it at this point
100+
// (it's not acquired in the other code paths)
101+
// So immediately release the GIL:
102+
let _thread_state = ffi::PyEval_SaveThread();
103+
// Note that the PyThreadState returned by PyEval_SaveThread is also held in TLS by the Python runtime,
104+
// and will be restored by PyGILState_Ensure.
106105
}
107106
});
108107
}
@@ -137,24 +136,56 @@ impl GILGuard {
137136
/// If PyO3 does not yet have a `GILPool` for tracking owned PyObject references, then this
138137
/// new `GILGuard` will also contain a `GILPool`.
139138
pub(crate) fn acquire() -> GILGuard {
140-
#[cfg(feature = "auto-initialize")]
141-
prepare_freethreaded_python();
142-
143-
#[cfg(not(feature = "auto-initialize"))]
144-
START.call_once_force(|_| unsafe {
145-
// Use call_once_force because if there is a panic because the interpreter is not
146-
// initialized, it's fine for the user to initialize the interpreter and retry.
147-
assert_ne!(
148-
ffi::Py_IsInitialized(),
149-
0,
150-
"The Python interpreter is not initalized and the `auto-initialize` feature is not enabled."
151-
);
152-
assert_ne!(
153-
ffi::PyEval_ThreadsInitialized(),
154-
0,
155-
"Python threading is not initalized and the `auto-initialize` feature is not enabled."
156-
);
157-
});
139+
// Maybe auto-initialize the GIL:
140+
// - If auto-initialize feature set and supported, try to initalize the interpreter.
141+
// - If the auto-initialize feature is set but unsupported, emit hard errors only when
142+
// the extension-module feature is not activated - extension modules don't care about
143+
// auto-initialize so this avoids breaking existing builds.
144+
// - Otherwise, just check the GIL is initialized.
145+
cfg_if::cfg_if! {
146+
if #[cfg(all(feature = "auto-initialize", Py_SHARED, not(PyPy)))] {
147+
prepare_freethreaded_python();
148+
} else if #[cfg(all(feature = "auto-initialize", not(feature = "extension-module"), not(Py_SHARED), not(rustdoc)))] {
149+
compile_error!(concat!(
150+
"The `auto-initialize` feature is not supported when linking Python ",
151+
"statically instead of with a shared library.\n",
152+
"\n",
153+
"Please disable the `auto-initialize` feature, for example by entering the following ",
154+
"in your cargo.toml:\n",
155+
"\n",
156+
" pyo3 = { version = \"0.13.0\", default-features = false }\n",
157+
"\n",
158+
"Alternatively, compile PyO3 using a Python distribution which contains a shared ",
159+
"libary."
160+
));
161+
} else if #[cfg(all(feature = "auto-initialize", not(feature = "extension-module"), PyPy, not(rustdoc)))] {
162+
compile_error!(concat!(
163+
"The `auto-initialize` feature is not supported by PyPy.\n",
164+
"\n",
165+
"Please disable the `auto-initialize` feature, for example by entering the following ",
166+
"in your cargo.toml:\n",
167+
"\n",
168+
" pyo3 = { version = \"0.13.0\", default-features = false }\n",
169+
));
170+
} else {
171+
// extension module feature enabled and PyPy or static linking
172+
// OR auto-initialize feature not enabled
173+
START.call_once_force(|_| unsafe {
174+
// Use call_once_force because if there is a panic because the interpreter is not
175+
// initialized, it's fine for the user to initialize the interpreter and retry.
176+
assert_ne!(
177+
ffi::Py_IsInitialized(),
178+
0,
179+
"The Python interpreter is not initalized and the `auto-initialize` feature is not enabled."
180+
);
181+
assert_ne!(
182+
ffi::PyEval_ThreadsInitialized(),
183+
0,
184+
"Python threading is not initalized and the `auto-initialize` feature is not enabled."
185+
);
186+
});
187+
}
188+
}
158189

159190
let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL
160191

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub use crate::conversion::{
146146
ToBorrowedObject, ToPyObject,
147147
};
148148
pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult};
149-
#[cfg(feature = "embedding")]
149+
#[cfg(all(Py_SHARED, not(PyPy)))]
150150
pub use crate::gil::prepare_freethreaded_python;
151151
pub use crate::gil::{GILGuard, GILPool};
152152
pub use crate::instance::{Py, PyNativeType, PyObject};

src/python.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ impl<'p> Python<'p> {
170170
/// `py: Python` to receive access to the GIL context in which the function is running.
171171
/// 2. Use [`Python::with_gil`](#method.with_gil) to run a closure with the GIL, acquiring
172172
/// only if needed.
173+
///
173174
/// **Note:** This return type from this function, `GILGuard`, is implemented as a RAII guard
174175
/// around the C-API Python_EnsureGIL. This means that multiple `acquire_gil()` calls are
175176
/// allowed, and will not deadlock. However, `GILGuard`s must be dropped in the reverse order

0 commit comments

Comments
 (0)