Skip to content

Commit

Permalink
gil: try to initialize threads on Python 3.6 if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Sep 17, 2021
1 parent b79a261 commit 70d829d
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed

- Fix building with a conda environment on Windows. [#1873](https://github.com/PyO3/pyo3/pull/1873)
- Fix panic on Python 3.6 when calling `Python::with_gil` with Python initialized but threading not initialized. [#1874](https://github.com/PyO3/pyo3/pull/1874)

## [0.14.5] - 2021-09-05

Expand Down
41 changes: 25 additions & 16 deletions src/gil.rs
Expand Up @@ -52,10 +52,12 @@ pub(crate) fn gil_is_acquired() -> bool {
/// software). Support for this is tracked on the
/// [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286).
///
/// Python 3.6 only: If the Python interpreter is initialized but Python threading is not, this
/// function will initialize Python 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.
/// - Python 3.6 only: If this function needs to initialize Python threading but the calling thread
/// is not the thread which initialized Python, this function will panic.
///
/// # Examples
/// ```rust
Expand All @@ -77,21 +79,28 @@ pub fn prepare_freethreaded_python() {
// 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);

// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t
// have to call it yourself anymore.
if cfg!(not(Py_3_7)) {
if cfg!(not(Py_3_7)) {
// Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() != 0 {
if ffi::PyEval_ThreadsInitialized() == 0 {
// We can only safely initialize threads if this thread holds the GIL.
assert!(
!ffi::PyGILState_GetThisThreadState().is_null(),
"Python threading is not initialized and cannot be initialized by this \
thread, because it is not the thread which initialized Python."
);
ffi::PyEval_InitThreads();
}
} else {
ffi::Py_InitializeEx(0);
ffi::PyEval_InitThreads();

// Release the GIL.
ffi::PyEval_SaveThread();
}
} else if ffi::Py_IsInitialized() == 0 {
// In Python 3.7 and up PyEval_InitThreads is irrelevant.
ffi::Py_InitializeEx(0);

// Release the GIL.
ffi::PyEval_SaveThread();
Expand All @@ -101,7 +110,7 @@ pub fn prepare_freethreaded_python() {

/// Executes the provided closure with an embedded Python interpreter.
///
/// This function intializes the Python interpreter, executes the provided closure, and then
/// This function initializes the Python interpreter, executes the provided closure, and then
/// finalizes the Python interpreter.
///
/// After execution all Python resources are cleaned up, and no further Python APIs can be called.
Expand All @@ -110,7 +119,7 @@ pub fn prepare_freethreaded_python() {
/// initialize correctly on the second run.)
///
/// # Panics
/// - If the Python interpreter is already initalized before calling this function.
/// - If the Python interpreter is already initialized before calling this function.
///
/// # Safety
/// - This function should only ever be called once per process (usually as part of the `main`
Expand Down
9 changes: 9 additions & 0 deletions tests/test_py36_init.rs
@@ -0,0 +1,9 @@
// This test checks Python initialization on python 3.6, so needs to be standalone in its own process.

#[cfg(not(PyPy))]
#[test]
fn test_py36_init_threads() {
unsafe { pyo3::ffi::Py_InitializeEx(0) };
pyo3::prepare_freethreaded_python();
assert_eq!(unsafe { pyo3::ffi::PyEval_ThreadsInitialized() }, 1);
}

0 comments on commit 70d829d

Please sign in to comment.