Skip to content

Commit 8e37d37

Browse files
authored
Merge pull request #1347 from davidhewitt/embedding
auto-initialize: new feature to control initializing Python
2 parents d9ccc98 + e0c35d1 commit 8e37d37

File tree

12 files changed

+195
-56
lines changed

12 files changed

+195
-56
lines changed

.github/workflows/ci.yml

Lines changed: 11 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 }}
@@ -125,6 +126,9 @@ jobs:
125126
env:
126127
RUST_BACKTRACE: 1
127128
RUSTFLAGS: "-D warnings"
129+
# TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy
130+
# Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
131+
PYO3_CI: 1
128132

129133
coverage:
130134
needs: [fmt]

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99
### Added
1010
- Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342)
1111
- Add FFI definitions `PyOS_BeforeFork`, `PyOS_AfterFork_Parent`, `PyOS_AfterFork_Child` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348)
12+
- Add `auto-initialize` feature to control whether PyO3 should automatically initialize an embedded Python interpreter. For compatibility this feature is enabled by default in PyO3 0.13.1, but is planned to become opt-in from PyO3 0.14.0. [#1347](https://github.com/PyO3/pyo3/pull/1347)
1213
- Add support for cross-compiling to Windows without needing `PYO3_CROSS_INCLUDE_DIR`. [#1350](https://github.com/PyO3/pyo3/pull/1350)
1314

1415
### Changed

Cargo.toml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,36 @@ assert_approx_eq = "1.1.0"
3333
trybuild = "1.0.23"
3434
rustversion = "1.0"
3535
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
36+
# features needed to run the PyO3 test suite
37+
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
3638

3739
[features]
38-
default = ["macros"]
39-
macros = ["ctor", "indoc", "inventory", "paste", "pyo3-macros", "unindent"]
40+
default = ["macros", "auto-initialize"]
41+
42+
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
43+
macros = ["pyo3-macros", "ctor", "indoc", "inventory", "paste", "unindent"]
44+
45+
# Use this feature when building an extension module.
46+
# It tells the linker to keep the python symbols unresolved,
47+
# so that the module can also be used with statically linked python interpreters.
48+
extension-module = []
49+
4050
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
4151
abi3 = []
52+
4253
# With abi3, we can manually set the minimum Python version.
4354
abi3-py36 = ["abi3-py37"]
4455
abi3-py37 = ["abi3-py38"]
4556
abi3-py38 = ["abi3-py39"]
4657
abi3-py39 = ["abi3"]
4758

59+
# Changes `Python::with_gil` and `Python::acquire_gil` to automatically initialize the
60+
# Python interpreter if needed.
61+
auto-initialize = []
62+
4863
# Optimizes PyObject to Vec conversion and so on.
4964
nightly = []
5065

51-
# Use this feature when building an extension module.
52-
# It tells the linker to keep the python symbols unresolved,
53-
# so that the module can also be used with statically linked python interpreters.
54-
extension-module = []
55-
5666
[workspace]
5767
members = [
5868
"pyo3-macros",

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ If you want your Rust application to create a Python interpreter internally and
104104
use it to run Python code, add `pyo3` to your `Cargo.toml` like this:
105105

106106
```toml
107-
[dependencies]
108-
pyo3 = "0.13.0"
107+
[dependencies.pyo3]
108+
version = "0.13.0"
109+
features = ["auto-initialize"]
109110
```
110111

111112
Example program displaying the value of `sys.version` and the current user name:

build.rs

Lines changed: 10 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
};
@@ -883,5 +887,11 @@ fn main() -> Result<()> {
883887
}
884888
}
885889

890+
// TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy
891+
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
892+
if env::var_os("PYO3_CI").is_some() {
893+
println!("cargo:rustc-cfg=__pyo3_ci");
894+
}
895+
886896
Ok(())
887897
}

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- [GIL, mutability and object types](types.md)
1717
- [Parallelism](parallelism.md)
1818
- [Debugging](debugging.md)
19+
- [Features Reference](features.md)
1920
- [Advanced Topics](advanced.md)
2021
- [Building and Distribution](building_and_distribution.md)
2122
- [PyPy support](pypy.md)

guide/src/advanced.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,3 @@ The caveat to these "owned references" is that Rust references do not normally c
1515
For most use cases this behaviour is invisible. Occasionally, however, users may need to clear memory usage sooner than PyO3 usually does. PyO3 exposes this functionality with the the `GILPool` struct. When a `GILPool` is dropped, ***all*** owned references created after the `GILPool` was created will be cleared.
1616

1717
The unsafe function `Python::new_pool` allows you to create a new `GILPool`. When doing this, you must be very careful to ensure that once the `GILPool` is dropped you do not retain access any owned references created after the `GILPool` was created.
18-
19-
## The `nightly` feature
20-
21-
The `pyo3/nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
22-
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object is a `PyBuffer`
23-
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.

guide/src/building_and_distribution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ There are two ways to distribute your module as a Python package: The old, [setu
4242

4343
By default, Python extension modules can only be used with the same Python version they were compiled against -- if you build an extension module with Python 3.5, you can't import it using Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`.
4444

45-
Note that [maturin] >= 0.9.0 or [setuptools-rust] >= 0.12.0 is going to support `abi3` wheels.
45+
Note that [maturin] >= 0.9.0 or [setuptools-rust] >= 0.11.4 support `abi3` wheels.
4646
See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://github.com/PyO3/setuptools-rust/pull/82) for more.
4747

4848
There are three steps involved in making use of `abi3` when building Python packages as wheels:

guide/src/features.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Features Reference
2+
3+
PyO3 provides a number of Cargo features to customise functionality. This chapter of the guide provides detail on each of them.
4+
5+
By default, the `macros` and `auto-initialize` features are enabled.
6+
7+
## Features for extension module authors
8+
9+
### `extension-module`
10+
11+
This feature is required when building a Python extension module using PyO3.
12+
13+
It tells PyO3's build script to skip linking against `libpython.so` on Unix platforms, where this must not be done.
14+
15+
See the [building and distribution](building_and_distribution.md#linking) section for further detail.
16+
17+
### `abi3`
18+
19+
This feature is used when building Python extension modules to create wheels which are compatible with multiple Python versions.
20+
21+
It restricts PyO3's API to a subset of the full Python API which is guaranteed by [PEP 384](https://www.python.org/dev/peps/pep-0384/) to be forwards-compatible with future Python versions.
22+
23+
See the [building and distribution](building_and_distribution.md#py_limited_apiabi3) section for further detail.
24+
25+
### `abi3-py36` / `abi3-py37` / `abi3-py38` / `abi3-py39`
26+
27+
These features are an extension of the `abi3` feature to specify the exact minimum Python version which the multiple-version-wheel will support.
28+
29+
See the [building and distribution](building_and_distribution.md#minimum-python-version-for-abi3) section for further detail.
30+
31+
## Features for embedding Python in Rust
32+
33+
### `auto-initalize`
34+
35+
This feature changes [`Python::with_gil`](https://docs.rs/pyo3/latest/pyo3/struct.Python.html#method.with_gil) and [`Python::acquire_gil`](https://docs.rs/pyo3/latest/pyo3/struct.Python.html#method.acquire_gil) to automatically initialize a Python interpreter (by calling [`prepare_freethreaded_python`](https://docs.rs/pyo3/latest/pyo3/fn.prepare_freethreaded_python.html)) if needed.
36+
37+
This feature is not needed for extension modules, but for compatibility it is enabled by default until at least the PyO3 0.14 release.
38+
39+
> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml.
40+
41+
## Advanced Features
42+
43+
### `macros`
44+
45+
This feature enables a dependency on the `pyo3-macros` crate, which provides the procedural macros portion of PyO3's API:
46+
47+
- `#[pymodule]`
48+
- `#[pyfunction]`
49+
- `#[pyclass]`
50+
- `#[pymethods]`
51+
- `#[pyproto]`
52+
- `#[derive(FromPyObject)]`
53+
54+
It also provides the `py_run!` macro.
55+
56+
These macros require a number of dependencies which may not be needed by users who just need PyO3 for Python FFI. Disabling this feature enables faster builds for those users, as these dependencies will not be built if this feature is disabled.
57+
58+
> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml.
59+
60+
### `nightly`
61+
62+
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
63+
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
64+
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.

src/gil.rs

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
//! Interaction with python's global interpreter lock
44
55
use crate::{ffi, internal_tricks::Unsendable, Python};
6-
use parking_lot::{const_mutex, Mutex};
6+
use parking_lot::{const_mutex, Mutex, Once};
77
use std::cell::{Cell, RefCell};
8-
use std::{mem::ManuallyDrop, ptr::NonNull, sync};
8+
use std::{mem::ManuallyDrop, ptr::NonNull};
99

10-
static START: sync::Once = sync::Once::new();
10+
static START: Once = Once::new();
1111

1212
thread_local! {
1313
/// This is a internal counter in pyo3 monitoring whether this thread has the GIL.
@@ -45,16 +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+
/// # Availability
49+
///
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.
54+
///
4855
/// # Panic
4956
/// If the Python interpreter is initialized but Python threading is not,
5057
/// a panic occurs.
5158
/// It is not possible to safely access the Python runtime unless the main
5259
/// thread (the thread which originally initialized Python) also initializes
5360
/// threading.
54-
///
55-
/// When writing an extension module, the `#[pymodule]` macro
56-
/// will ensure that Python threading is initialized.
57-
///
61+
#[cfg(all(Py_SHARED, not(PyPy)))]
5862
pub fn prepare_freethreaded_python() {
5963
// Protect against race conditions when Python is not yet initialized
6064
// and multiple threads concurrently call 'prepare_freethreaded_python()'.
@@ -72,34 +76,29 @@ pub fn prepare_freethreaded_python() {
7276
// Note that the 'main thread' notion in Python isn't documented properly;
7377
// and running Python without one is not officially supported.
7478

75-
// PyPy does not support the embedding API
76-
#[cfg(not(PyPy))]
77-
{
78-
ffi::Py_InitializeEx(0);
79-
80-
// Make sure Py_Finalize will be called before exiting.
81-
extern "C" fn finalize() {
82-
unsafe {
83-
if ffi::Py_IsInitialized() != 0 {
84-
ffi::PyGILState_Ensure();
85-
ffi::Py_Finalize();
86-
}
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();
8787
}
8888
}
89-
libc::atexit(finalize);
9089
}
90+
libc::atexit(finalize);
9191

9292
// > Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have
9393
// > to call it yourself anymore.
9494
#[cfg(not(Py_3_7))]
9595
if ffi::PyEval_ThreadsInitialized() == 0 {
9696
ffi::PyEval_InitThreads();
9797
}
98-
// PyEval_InitThreads() will acquire the GIL,
99-
// but we don't want to hold it at this point
98+
99+
// Py_InitializeEx() will acquire the GIL, but we don't want to hold it at this point
100100
// (it's not acquired in the other code paths)
101101
// So immediately release the GIL:
102-
#[cfg(not(PyPy))]
103102
let _thread_state = ffi::PyEval_SaveThread();
104103
// Note that the PyThreadState returned by PyEval_SaveThread is also held in TLS by the Python runtime,
105104
// and will be restored by PyGILState_Ensure.
@@ -137,7 +136,51 @@ 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-
prepare_freethreaded_python();
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(__pyo3_ci)))] {
149+
compile_error!(concat!(
150+
"The `auto-initialize` feature is not supported when linking Python ",
151+
"statically instead of with a shared library.\n\n",
152+
"Please disable the `auto-initialize` feature, for example by entering the following ",
153+
"in your cargo.toml:\n\n",
154+
" pyo3 = { version = \"0.13.0\", default-features = false }\n\n",
155+
"Alternatively, compile PyO3 using a Python distribution which contains a shared ",
156+
"libary."
157+
));
158+
} else if #[cfg(all(feature = "auto-initialize", not(feature = "extension-module"), PyPy, not(__pyo3_ci)))] {
159+
compile_error!(concat!(
160+
"The `auto-initialize` feature is not supported by PyPy.\n\n",
161+
"Please disable the `auto-initialize` feature, for example by entering the following ",
162+
"in your cargo.toml:\n\n",
163+
" pyo3 = { version = \"0.13.0\", default-features = false }",
164+
));
165+
} else {
166+
// extension module feature enabled and PyPy or static linking
167+
// OR auto-initialize feature not enabled
168+
START.call_once_force(|_| unsafe {
169+
// Use call_once_force because if there is a panic because the interpreter is not
170+
// initialized, it's fine for the user to initialize the interpreter and retry.
171+
assert_ne!(
172+
ffi::Py_IsInitialized(),
173+
0,
174+
"The Python interpreter is not initalized and the `auto-initialize` feature is not enabled."
175+
);
176+
assert_ne!(
177+
ffi::PyEval_ThreadsInitialized(),
178+
0,
179+
"Python threading is not initalized and the `auto-initialize` feature is not enabled."
180+
);
181+
});
182+
}
183+
}
141184

142185
let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL
143186

src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@
114114
//! Add `pyo3` to your `Cargo.toml`:
115115
//!
116116
//! ```toml
117-
//! [dependencies]
118-
//! pyo3 = "0.13.0"
117+
//! [dependencies.pyo3]
118+
//! version = "0.13.0"
119+
//! features = ["auto-initialize"]
119120
//! ```
120121
//!
121122
//! Example program displaying the value of `sys.version`:
@@ -145,12 +146,14 @@ pub use crate::conversion::{
145146
ToBorrowedObject, ToPyObject,
146147
};
147148
pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult};
149+
#[cfg(all(Py_SHARED, not(PyPy)))]
150+
pub use crate::gil::prepare_freethreaded_python;
148151
pub use crate::gil::{GILGuard, GILPool};
149152
pub use crate::instance::{Py, PyNativeType, PyObject};
150153
pub use crate::pycell::{PyCell, PyRef, PyRefMut};
151154
pub use crate::pyclass::PyClass;
152155
pub use crate::pyclass_init::PyClassInitializer;
153-
pub use crate::python::{prepare_freethreaded_python, Python, PythonVersionInfo};
156+
pub use crate::python::{Python, PythonVersionInfo};
154157
pub use crate::type_object::{type_flags, PyTypeInfo};
155158
// Since PyAny is as important as PyObject, we expose it to the top level.
156159
pub use crate::types::PyAny;

0 commit comments

Comments
 (0)