pyo3/interpreter_lifecycle.rs
1// TODO https://github.com/PyO3/pyo3/issues/5487
2#![allow(clippy::undocumented_unsafe_blocks)]
3
4#[cfg(not(any(PyPy, GraalPy)))]
5use crate::{ffi, internal::state::AttachGuard, Python};
6
7static START: std::sync::Once = std::sync::Once::new();
8
9#[cfg(not(any(PyPy, GraalPy)))]
10pub(crate) fn initialize() {
11 // Protect against race conditions when Python is not yet initialized and multiple threads
12 // concurrently call 'initialize()'. Note that we do not protect against
13 // concurrent initialization of the Python runtime by other users of the Python C API.
14 START.call_once_force(|_| unsafe {
15 // Use call_once_force because if initialization panics, it's okay to try again.
16 if ffi::Py_IsInitialized() == 0 {
17 ffi::Py_InitializeEx(0);
18
19 // Release the GIL.
20 ffi::PyEval_SaveThread();
21 }
22 });
23}
24
25/// Executes the provided closure with an embedded Python interpreter.
26///
27/// This function initializes the Python interpreter, executes the provided closure, and then
28/// finalizes the Python interpreter.
29///
30/// After execution all Python resources are cleaned up, and no further Python APIs can be called.
31/// Because many Python modules implemented in C do not support multiple Python interpreters in a
32/// single process, it is not safe to call this function more than once. (Many such modules will not
33/// initialize correctly on the second run.)
34///
35/// # Panics
36/// - If the Python interpreter is already initialized before calling this function.
37///
38/// # Safety
39/// - This function should only ever be called once per process (usually as part of the `main`
40/// function). It is also not thread-safe.
41/// - No Python APIs can be used after this function has finished executing.
42/// - The return value of the closure must not contain any Python value, _including_ `PyResult`.
43///
44/// # Examples
45///
46/// ```rust
47/// unsafe {
48/// pyo3::with_embedded_python_interpreter(|py| {
49/// if let Err(e) = py.run(c"print('Hello World')", None, None) {
50/// // We must make sure to not return a `PyErr`!
51/// e.print(py);
52/// }
53/// });
54/// }
55/// ```
56#[cfg(not(any(PyPy, GraalPy)))]
57pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
58where
59 F: for<'p> FnOnce(Python<'p>) -> R,
60{
61 assert_eq!(
62 unsafe { ffi::Py_IsInitialized() },
63 0,
64 "called `with_embedded_python_interpreter` but a Python interpreter is already running."
65 );
66
67 unsafe { ffi::Py_InitializeEx(0) };
68
69 let result = {
70 let guard = unsafe { AttachGuard::assume() };
71 let py = guard.python();
72 // Import the threading module - this ensures that it will associate this thread as the "main"
73 // thread, which is important to avoid an `AssertionError` at finalization.
74 py.import("threading").unwrap();
75
76 // Execute the closure.
77 f(py)
78 };
79
80 // Finalize the Python interpreter.
81 unsafe { ffi::Py_Finalize() };
82
83 result
84}
85
86/// If PyO3 is currently running `Py_InitializeEx` inside the `Once` guard,
87/// block until it completes. Needed because `Py_InitializeEx` sets the
88/// `initialized` flag in the interpreter to true before it finishes all its
89/// steps (in particular, before it imports `site.py`).
90///
91/// This must only be called after `Py_IsInitialized()` has returned true.
92///
93/// If the `Once` was never started (e.g. the interpreter was initialized
94/// externally, not through PyO3), `call_once` runs the empty closure and
95/// returns — this is fine because `initialize()` checks
96/// `Py_IsInitialized()` inside its closure and skips `Py_InitializeEx` if
97/// the interpreter is already running. If the `Once` is currently in
98/// progress (another thread is inside `initialize()`), `call_once` blocks
99/// until it completes.
100pub(crate) fn wait_for_initialization() {
101 // TODO: use START.wait_force() on MSRV 1.86
102 // TODO: may not be needed on Python 3.15 (https://github.com/python/cpython/pull/146303)
103 START.call_once(|| {
104 assert_ne!(unsafe { crate::ffi::Py_IsInitialized() }, 0);
105 });
106}
107
108pub(crate) fn ensure_initialized() {
109 // Maybe auto-initialize the interpreter:
110 // - If auto-initialize feature set and supported, try to initialize the interpreter.
111 // - If the auto-initialize feature is set but unsupported, emit hard errors only when the
112 // extension-module feature is not activated - extension modules don't care about
113 // auto-initialize so this avoids breaking existing builds.
114 // - Otherwise, just check the interpreter is initialized.
115 #[cfg(all(feature = "auto-initialize", not(any(PyPy, GraalPy))))]
116 {
117 initialize();
118 }
119 #[cfg(not(all(feature = "auto-initialize", not(any(PyPy, GraalPy)))))]
120 {
121 // This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need
122 // to specify `--features auto-initialize` manually). Tests within the crate itself
123 // all depend on the auto-initialize feature for conciseness but Cargo does not
124 // provide a mechanism to specify required features for tests.
125 #[cfg(not(any(PyPy, GraalPy)))]
126 if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
127 initialize();
128 }
129
130 START.call_once_force(|_| unsafe {
131 // Use call_once_force because if there is a panic because the interpreter is
132 // not initialized, it's fine for the user to initialize the interpreter and
133 // retry.
134 assert_ne!(
135 crate::ffi::Py_IsInitialized(),
136 0,
137 "The Python interpreter is not initialized and the `auto-initialize` \
138 feature is not enabled.\n\n\
139 Consider calling `Python::initialize()` before attempting \
140 to use Python APIs."
141 );
142 });
143 }
144}