From 7c61c9b7f9d46592cb9f3216038fbf672f8eb5f3 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 24 Nov 2020 22:37:35 +0000 Subject: [PATCH 1/2] gil: tidy ups to finalization --- CHANGELOG.md | 4 ++ guide/src/faq.md | 23 ++++++ guide/src/features.md | 2 + src/gil.rs | 164 +++++++++++++++++++++++++++++++++++------- src/lib.rs | 2 +- 5 files changed, 170 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a49c7e425b0..909938767f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +- Add `prepare_freethreaded_python_without_finalizer` to initalize a Python interpreter while avoiding potential finalization issues in C-extensions like SciPy. [#1355](https://github.com/PyO3/pyo3/pull/1355) - Add `serde` feature to support `Serialize/Deserialize` for `Py`. [#1366](https://github.com/PyO3/pyo3/pull/1366) +### Changed +- Call `Py_Finalize` in the same thread as `Py_InitializeEx` inside `prepare_freethreaded_python`. [#1355](https://github.com/PyO3/pyo3/pull/1355) + ## [0.13.1] - 2021-01-10 ### Added - 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) diff --git a/guide/src/faq.md b/guide/src/faq.md index 1bda457fceb..8ee8d2629ff 100644 --- a/guide/src/faq.md +++ b/guide/src/faq.md @@ -56,3 +56,26 @@ crate-type = ["cdylib", "rlib"] This is because Ctrl-C raises a SIGINT signal, which is handled by the calling Python process by simply setting a flag to action upon later. This flag isn't checked while Rust code called from Python is executing, only once control returns to the Python interpreter. You can give the Python interpreter a chance to process the signal properly by calling `Python::check_signals`. It's good practice to call this function regularly if you have a long-running Rust function so that your users can cancel it. + +## Importing C extensions like Tensorflow and SciPy cause crashes on program exit for my Python embedded in Rust. + +This is because deinitialization is extremely sensitive to ordering, and if the sequence is wrong it's easy for C extensions to inadvertently cause errors like double-free. This will lead to crashes like `SIGSEGV` on program exit. + +If you are experiencing these errors, the workaround is to make PyO3 not call `Py_Finalize` on program exit. This can be done by the following steps: + +1. Disable the `auto-initialize` feature in your `Cargo.toml`: + +```toml +# Cargo.toml +[dependencies] +pyo3 = { version = "0.13.0", default-features = false } +``` + +2. Call [`pyo3::prepare_freethreaded_python_without_finalizer`] before attempting to call any Python APIs. + + ```rust + fn main() { + pyo3::call_freethreaded_python_without_finalizer(); + // Rest of your program to follow. + } + ``` diff --git a/guide/src/features.md b/guide/src/features.md index a7b620946b0..8a6e98a4145 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -36,6 +36,8 @@ This feature changes [`Python::with_gil`](https://docs.rs/pyo3/latest/pyo3/struc This feature is not needed for extension modules, but for compatibility it is enabled by default until at least the PyO3 0.14 release. +If you choose not to enable this feature, you should call `pyo3::prepare_freethreaded_python()` before attempting to call any other Python APIs. + > This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml. ## Advanced Features diff --git a/src/gil.rs b/src/gil.rs index aaedbb96d7c..82afb4a2af8 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -42,66 +42,182 @@ pub(crate) fn gil_is_acquired() -> bool { /// Python signal handling depends on the notion of a 'main thread', which must be /// the thread that initializes the Python interpreter. /// +/// Additionally, this function will register an `atexit` callback to finalize the Python +/// interpreter. Usually this is desirable - it flushes Python buffers and ensures that all +/// Python objects are cleaned up appropriately. Some C extensions can have memory issues during +/// finalization, so you may get crashes on program exit if your embedded Python program uses +/// these extensions. If this is the case, you should use [`prepare_freethreaded_python_without_finalizer`] +/// which does not register the `atexit` callback. +/// /// If both the Python interpreter and Python threading are already initialized, /// this function has no effect. /// /// # Availability -/// /// This function is only available when linking against Python distributions that contain a /// shared library. /// /// This function is not available on PyPy. /// -/// # Panic -/// If the Python interpreter is initialized but Python threading is not, -/// a panic occurs. -/// It is not possible to safely access the Python runtime unless the main -/// thread (the thread which originally initialized Python) also initializes -/// threading. +/// # Panics +/// - If the Python interpreter is initialized but Python threading is not, +/// a panic occurs. +/// It is not possible to safely access the Python runtime unless the main +/// thread (the thread which originally initialized Python) also initializes +/// threading. +/// +/// # Example +/// ```rust +/// use pyo3::prelude::*; +/// +/// # #[allow(clippy::needless_doctest_main)] +/// fn main() { +/// pyo3::prepare_freethreaded_python(); +/// Python::with_gil(|py| { +/// py.run("print('Hello World')", None, None) +/// }); +/// } +/// ``` #[cfg(all(Py_SHARED, not(PyPy)))] pub fn prepare_freethreaded_python() { // Protect against race conditions when Python is not yet initialized // and multiple threads concurrently call 'prepare_freethreaded_python()'. // Note that we do not protect against concurrent initialization of the Python runtime // by other users of the Python C API. - START.call_once(|| unsafe { + START.call_once_force(|_| unsafe { + // Use call_once_force because if initialization panics, it's okay to try again. if ffi::Py_IsInitialized() != 0 { // If Python is already initialized, we expect Python threading to also be initialized, // as we can't make the existing Python main thread acquire the GIL. assert_ne!(ffi::PyEval_ThreadsInitialized(), 0); } else { + use parking_lot::Condvar; + // Initialize Python. // We use Py_InitializeEx() with initsigs=0 to disable Python signal handling. // Signal handling depends on the notion of a 'main thread', which doesn't exist in this case. // Note that the 'main thread' notion in Python isn't documented properly; // and running Python without one is not officially supported. + static INITIALIZATION_THREAD_SIGNAL: Condvar = Condvar::new(); + static INITIALIZATION_THREAD_MUTEX: Mutex<()> = const_mutex(()); - ffi::Py_InitializeEx(0); + // This thread will be responsible for initialization and finalization of the + // Python interpreter. + // + // This is necessary because Python's `threading` module requires that the same + // thread which started the interpreter also calls finalize. (If this is not the case, + // an AssertionError is raised during the Py_Finalize call.) + unsafe fn initialization_thread() { + let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); + + ffi::Py_InitializeEx(0); + + #[cfg(not(Py_3_7))] // Called by Py_InitializeEx in Python 3.7 and up. + ffi::PyEval_InitThreads(); + + // Import the threading module - this ensures that it will associate this + // thread as the "main" thread. + { + let pool = GILPool::new(); + pool.python().import("threading").unwrap(); + } + + // Release the GIL, notify the original calling thread that Python is now + // initialized, and wait for notification to begin finalization. + let tstate = ffi::PyEval_SaveThread(); + + INITIALIZATION_THREAD_SIGNAL.notify_one(); + INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); + + // Signal to finalize received. + ffi::PyEval_RestoreThread(tstate); + ffi::Py_Finalize(); + + INITIALIZATION_THREAD_SIGNAL.notify_one(); + } + + let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); + std::thread::spawn(|| initialization_thread()); + INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); // Make sure Py_Finalize will be called before exiting. - extern "C" fn finalize() { + extern "C" fn finalize_callback() { unsafe { if ffi::Py_IsInitialized() != 0 { + // Before blocking on the finalization thread, ensure this thread does not + // hold the GIL - otherwise can result in a deadlock! ffi::PyGILState_Ensure(); - ffi::Py_Finalize(); + ffi::PyEval_SaveThread(); + + // Notify initialization_thread to finalize, and wait. + let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); + INITIALIZATION_THREAD_SIGNAL.notify_one(); + INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); + assert_eq!(ffi::Py_IsInitialized(), 0); } } } - libc::atexit(finalize); - // > Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have - // > to call it yourself anymore. - #[cfg(not(Py_3_7))] - if ffi::PyEval_ThreadsInitialized() == 0 { - ffi::PyEval_InitThreads(); - } + libc::atexit(finalize_callback); + } + }); +} + +/// Prepares the use of Python in a free-threaded context. +/// +/// If the Python interpreter is not already initialized, this function +/// will initialize it with disabled signal handling +/// (Python will not raise the `KeyboardInterrupt` exception). +/// Python signal handling depends on the notion of a 'main thread', which must be +/// the thread that initializes the Python interpreter. +/// +/// If both the Python interpreter and Python threading are already initialized, +/// this function has no effect. +/// +/// # Availability +/// This function is only available when linking against Python distributions that contain a +/// shared library. +/// +/// This function is not available on PyPy. +/// +/// # Panics +/// - If the Python interpreter is initialized but Python threading is not, +/// a panic occurs. +/// It is not possible to safely access the Python runtime unless the main +/// thread (the thread which originally initialized Python) also initializes +/// threading. +/// +/// # Example +/// ```rust +/// use pyo3::prelude::*; +/// +/// # #[allow(clippy::needless_doctest_main)] +/// fn main() { +/// pyo3::prepare_freethreaded_python_without_finalizer(); +/// Python::with_gil(|py| { +/// py.run("print('Hello World')", None, None) +/// }); +/// } +/// ``` +#[cfg(all(Py_SHARED, not(PyPy)))] +pub fn prepare_freethreaded_python_without_finalizer() { + // Protect against race conditions when Python is not yet initialized + // and multiple threads concurrently call 'prepare_freethreaded_python()'. + // Note that we do not protect against concurrent initialization of the Python runtime + // by other users of the Python C API. + START.call_once_force(|_| unsafe { + // Use call_once_force because if initialization panics, it's okay to try again. + if ffi::Py_IsInitialized() != 0 { + // If Python is already initialized, we expect Python threading to also be initialized, + // as we can't make the existing Python main thread acquire the GIL. + assert_ne!(ffi::PyEval_ThreadsInitialized(), 0); + } else { + ffi::Py_InitializeEx(0); + + #[cfg(not(Py_3_7))] // Called by Py_InitializeEx in Python 3.7 and up. + ffi::PyEval_InitThreads(); - // Py_InitializeEx() will acquire the GIL, but we don't want to hold it at this point - // (it's not acquired in the other code paths) - // So immediately release the GIL: - let _thread_state = ffi::PyEval_SaveThread(); - // Note that the PyThreadState returned by PyEval_SaveThread is also held in TLS by the Python runtime, - // and will be restored by PyGILState_Ensure. + // Release the GIL. + ffi::PyEval_SaveThread(); } }); } diff --git a/src/lib.rs b/src/lib.rs index afa599f2052..1f83f91f80e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,7 +152,7 @@ pub use crate::conversion::{ }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; #[cfg(all(Py_SHARED, not(PyPy)))] -pub use crate::gil::prepare_freethreaded_python; +pub use crate::gil::{prepare_freethreaded_python, prepare_freethreaded_python_without_finalizer}; pub use crate::gil::{GILGuard, GILPool}; pub use crate::instance::{Py, PyNativeType, PyObject}; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; From dc7bcdaf9b26789404ee54124d6c57791e7d6d69 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 7 Jan 2021 23:17:03 +0000 Subject: [PATCH 2/2] gil: move finalization from prepare_freethreaded_python to with_embedded_python_interpreter --- CHANGELOG.md | 4 +- guide/src/faq.md | 23 ------ src/gil.rs | 197 ++++++++++++++++++----------------------------- src/lib.rs | 2 +- 4 files changed, 78 insertions(+), 148 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 909938767f1..5dcc38fc8c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added -- Add `prepare_freethreaded_python_without_finalizer` to initalize a Python interpreter while avoiding potential finalization issues in C-extensions like SciPy. [#1355](https://github.com/PyO3/pyo3/pull/1355) +- Add unsafe API `with_embedded_python_interpreter` to initalize a Python interpreter, execute a closure, and finalize the interpreter. [#1355](https://github.com/PyO3/pyo3/pull/1355) - Add `serde` feature to support `Serialize/Deserialize` for `Py`. [#1366](https://github.com/PyO3/pyo3/pull/1366) ### Changed -- Call `Py_Finalize` in the same thread as `Py_InitializeEx` inside `prepare_freethreaded_python`. [#1355](https://github.com/PyO3/pyo3/pull/1355) +- `prepare_freethreaded_python` will no longer register an `atexit` handler to call `Py_Finalize`. [#1355](https://github.com/PyO3/pyo3/pull/1355) ## [0.13.1] - 2021-01-10 ### Added diff --git a/guide/src/faq.md b/guide/src/faq.md index 8ee8d2629ff..1bda457fceb 100644 --- a/guide/src/faq.md +++ b/guide/src/faq.md @@ -56,26 +56,3 @@ crate-type = ["cdylib", "rlib"] This is because Ctrl-C raises a SIGINT signal, which is handled by the calling Python process by simply setting a flag to action upon later. This flag isn't checked while Rust code called from Python is executing, only once control returns to the Python interpreter. You can give the Python interpreter a chance to process the signal properly by calling `Python::check_signals`. It's good practice to call this function regularly if you have a long-running Rust function so that your users can cancel it. - -## Importing C extensions like Tensorflow and SciPy cause crashes on program exit for my Python embedded in Rust. - -This is because deinitialization is extremely sensitive to ordering, and if the sequence is wrong it's easy for C extensions to inadvertently cause errors like double-free. This will lead to crashes like `SIGSEGV` on program exit. - -If you are experiencing these errors, the workaround is to make PyO3 not call `Py_Finalize` on program exit. This can be done by the following steps: - -1. Disable the `auto-initialize` feature in your `Cargo.toml`: - -```toml -# Cargo.toml -[dependencies] -pyo3 = { version = "0.13.0", default-features = false } -``` - -2. Call [`pyo3::prepare_freethreaded_python_without_finalizer`] before attempting to call any Python APIs. - - ```rust - fn main() { - pyo3::call_freethreaded_python_without_finalizer(); - // Rest of your program to follow. - } - ``` diff --git a/src/gil.rs b/src/gil.rs index 82afb4a2af8..2d69bba3b54 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -42,13 +42,6 @@ pub(crate) fn gil_is_acquired() -> bool { /// Python signal handling depends on the notion of a 'main thread', which must be /// the thread that initializes the Python interpreter. /// -/// Additionally, this function will register an `atexit` callback to finalize the Python -/// interpreter. Usually this is desirable - it flushes Python buffers and ensures that all -/// Python objects are cleaned up appropriately. Some C extensions can have memory issues during -/// finalization, so you may get crashes on program exit if your embedded Python program uses -/// these extensions. If this is the case, you should use [`prepare_freethreaded_python_without_finalizer`] -/// which does not register the `atexit` callback. -/// /// If both the Python interpreter and Python threading are already initialized, /// this function has no effect. /// @@ -79,10 +72,9 @@ pub(crate) fn gil_is_acquired() -> bool { /// ``` #[cfg(all(Py_SHARED, not(PyPy)))] pub fn prepare_freethreaded_python() { - // Protect against race conditions when Python is not yet initialized - // and multiple threads concurrently call 'prepare_freethreaded_python()'. - // Note that we do not protect against concurrent initialization of the Python runtime - // by other users of the Python C API. + // Protect against race conditions when Python is not yet initialized and multiple threads + // concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against + // concurrent initialization of the Python runtime by other users of the Python C API. START.call_once_force(|_| unsafe { // Use call_once_force because if initialization panics, it's okay to try again. if ffi::Py_IsInitialized() != 0 { @@ -90,101 +82,45 @@ pub fn prepare_freethreaded_python() { // as we can't make the existing Python main thread acquire the GIL. assert_ne!(ffi::PyEval_ThreadsInitialized(), 0); } else { - use parking_lot::Condvar; - - // Initialize Python. - // We use Py_InitializeEx() with initsigs=0 to disable Python signal handling. - // Signal handling depends on the notion of a 'main thread', which doesn't exist in this case. - // Note that the 'main thread' notion in Python isn't documented properly; - // and running Python without one is not officially supported. - static INITIALIZATION_THREAD_SIGNAL: Condvar = Condvar::new(); - static INITIALIZATION_THREAD_MUTEX: Mutex<()> = const_mutex(()); - - // This thread will be responsible for initialization and finalization of the - // Python interpreter. - // - // This is necessary because Python's `threading` module requires that the same - // thread which started the interpreter also calls finalize. (If this is not the case, - // an AssertionError is raised during the Py_Finalize call.) - unsafe fn initialization_thread() { - let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); - - ffi::Py_InitializeEx(0); - - #[cfg(not(Py_3_7))] // Called by Py_InitializeEx in Python 3.7 and up. - ffi::PyEval_InitThreads(); - - // Import the threading module - this ensures that it will associate this - // thread as the "main" thread. - { - let pool = GILPool::new(); - pool.python().import("threading").unwrap(); - } - - // Release the GIL, notify the original calling thread that Python is now - // initialized, and wait for notification to begin finalization. - let tstate = ffi::PyEval_SaveThread(); - - INITIALIZATION_THREAD_SIGNAL.notify_one(); - INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); - - // Signal to finalize received. - ffi::PyEval_RestoreThread(tstate); - ffi::Py_Finalize(); - - INITIALIZATION_THREAD_SIGNAL.notify_one(); - } - - let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); - std::thread::spawn(|| initialization_thread()); - INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); + ffi::Py_InitializeEx(0); - // Make sure Py_Finalize will be called before exiting. - extern "C" fn finalize_callback() { - unsafe { - if ffi::Py_IsInitialized() != 0 { - // Before blocking on the finalization thread, ensure this thread does not - // hold the GIL - otherwise can result in a deadlock! - ffi::PyGILState_Ensure(); - ffi::PyEval_SaveThread(); - - // Notify initialization_thread to finalize, and wait. - let mut guard = INITIALIZATION_THREAD_MUTEX.lock(); - INITIALIZATION_THREAD_SIGNAL.notify_one(); - INITIALIZATION_THREAD_SIGNAL.wait(&mut guard); - assert_eq!(ffi::Py_IsInitialized(), 0); - } - } + // Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t + // have to call it yourself anymore. + #[cfg(not(Py_3_7))] + if ffi::PyEval_ThreadsInitialized() == 0 { + ffi::PyEval_InitThreads(); } - libc::atexit(finalize_callback); + // Release the GIL. + ffi::PyEval_SaveThread(); } }); } -/// Prepares the use of Python in a free-threaded context. +/// Executes the provided closure with an embedded Python interpreter. /// -/// If the Python interpreter is not already initialized, this function -/// will initialize it with disabled signal handling -/// (Python will not raise the `KeyboardInterrupt` exception). -/// Python signal handling depends on the notion of a 'main thread', which must be -/// the thread that initializes the Python interpreter. +/// This function intializes the Python interpreter, executes the provided closure, and then +/// finalizes the Python interpreter. /// -/// If both the Python interpreter and Python threading are already initialized, -/// this function has no effect. +/// After execution all Python resources are cleaned up, and no further Python APIs can be called. +/// Because many Python modules implemented in C do not support multiple Python interpreters in a +/// single process, it is not safe to call this function more than once. (Many such modules will not +/// initialize correctly on the second run.) /// /// # Availability -/// This function is only available when linking against Python distributions that contain a -/// shared library. +/// This function is only available when linking against Python distributions that contain a shared +/// library. /// /// This function is not available on PyPy. /// /// # Panics -/// - If the Python interpreter is initialized but Python threading is not, -/// a panic occurs. -/// It is not possible to safely access the Python runtime unless the main -/// thread (the thread which originally initialized Python) also initializes -/// threading. +/// - If the Python interpreter is already initalized before calling this function. +/// +/// # Safety +/// - This function should only ever be called once per process (usually as part of the `main` +/// function). It is also not thread-safe. +/// - No Python APIs can be used after this function has finished executing. +/// - The return value of the closure must not contain any Python value, _including_ `PyResult`. /// /// # Example /// ```rust @@ -192,38 +128,54 @@ pub fn prepare_freethreaded_python() { /// /// # #[allow(clippy::needless_doctest_main)] /// fn main() { -/// pyo3::prepare_freethreaded_python_without_finalizer(); -/// Python::with_gil(|py| { -/// py.run("print('Hello World')", None, None) -/// }); +/// unsafe { +/// pyo3::with_embedded_python_interpreter(|py| { +/// py.run("print('Hello World')", None, None) +/// }); +/// } /// } /// ``` #[cfg(all(Py_SHARED, not(PyPy)))] -pub fn prepare_freethreaded_python_without_finalizer() { - // Protect against race conditions when Python is not yet initialized - // and multiple threads concurrently call 'prepare_freethreaded_python()'. - // Note that we do not protect against concurrent initialization of the Python runtime - // by other users of the Python C API. - START.call_once_force(|_| unsafe { - // Use call_once_force because if initialization panics, it's okay to try again. - if ffi::Py_IsInitialized() != 0 { - // If Python is already initialized, we expect Python threading to also be initialized, - // as we can't make the existing Python main thread acquire the GIL. - assert_ne!(ffi::PyEval_ThreadsInitialized(), 0); - } else { - ffi::Py_InitializeEx(0); +pub unsafe fn with_embedded_python_interpreter(f: F) -> R +where + F: for<'p> FnOnce(Python<'p>) -> R, +{ + assert_eq!( + ffi::Py_IsInitialized(), + 0, + "called `with_embedded_python_interpreter` but a Python interpreter is already running." + ); + + ffi::Py_InitializeEx(0); + + // Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have to + // call it yourself anymore. + #[cfg(not(Py_3_7))] + if ffi::PyEval_ThreadsInitialized() == 0 { + ffi::PyEval_InitThreads(); + } - #[cfg(not(Py_3_7))] // Called by Py_InitializeEx in Python 3.7 and up. - ffi::PyEval_InitThreads(); + // Safe: the GIL is already held because of the Py_IntializeEx call. + let pool = GILPool::new(); - // Release the GIL. - ffi::PyEval_SaveThread(); - } - }); + // Import the threading module - this ensures that it will associate this thread as the "main" + // thread, which is important to avoid an `AssertionError` at finalization. + pool.python().import("threading").unwrap(); + + // Execute the closure. + let result = f(pool.python()); + + // Drop the pool before finalizing. + drop(pool); + + // Finalize the Python interpreter. + ffi::Py_Finalize(); + + result } -/// RAII type that represents the Global Interpreter Lock acquisition. To get hold of a value -/// of this type, see [`Python::acquire_gil`](struct.Python.html#method.acquire_gil). +/// RAII type that represents the Global Interpreter Lock acquisition. To get hold of a value of +/// this type, see [`Python::acquire_gil`](struct.Python.html#method.acquire_gil). /// /// # Example /// ``` @@ -249,13 +201,13 @@ impl GILGuard { /// PyO3 internal API for acquiring the GIL. The public API is Python::acquire_gil. /// - /// If PyO3 does not yet have a `GILPool` for tracking owned PyObject references, then this - /// new `GILGuard` will also contain a `GILPool`. + /// If PyO3 does not yet have a `GILPool` for tracking owned PyObject references, then this new + /// `GILGuard` will also contain a `GILPool`. pub(crate) fn acquire() -> GILGuard { // Maybe auto-initialize the GIL: // - If auto-initialize feature set and supported, try to initalize the interpreter. - // - If the auto-initialize feature is set but unsupported, emit hard errors only when - // the extension-module feature is not activated - extension modules don't care about + // - If the auto-initialize feature is set but unsupported, emit hard errors only when the + // extension-module feature is not activated - extension modules don't care about // auto-initialize so this avoids breaking existing builds. // - Otherwise, just check the GIL is initialized. cfg_if::cfg_if! { @@ -286,8 +238,9 @@ impl GILGuard { // extension module feature enabled and PyPy or static linking // OR auto-initialize feature not enabled START.call_once_force(|_| unsafe { - // Use call_once_force because if there is a panic because the interpreter is not - // initialized, it's fine for the user to initialize the interpreter and retry. + // Use call_once_force because if there is a panic because the interpreter is + // not initialized, it's fine for the user to initialize the interpreter and + // retry. assert_ne!( ffi::Py_IsInitialized(), 0, diff --git a/src/lib.rs b/src/lib.rs index 1f83f91f80e..462d3249634 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,7 +152,7 @@ pub use crate::conversion::{ }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; #[cfg(all(Py_SHARED, not(PyPy)))] -pub use crate::gil::{prepare_freethreaded_python, prepare_freethreaded_python_without_finalizer}; +pub use crate::gil::{prepare_freethreaded_python, with_embedded_python_interpreter}; pub use crate::gil::{GILGuard, GILPool}; pub use crate::instance::{Py, PyNativeType, PyObject}; pub use crate::pycell::{PyCell, PyRef, PyRefMut};