|
| 1 | +// Copyright (c) 2023 The pybind Community. |
| 2 | + |
| 3 | +#pragma once |
| 4 | + |
| 5 | +#include "detail/common.h" |
| 6 | +#include "gil.h" |
| 7 | + |
| 8 | +#include <cassert> |
| 9 | +#include <mutex> |
| 10 | + |
| 11 | +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) |
| 12 | + |
| 13 | +// Use the `gil_safe_call_once_and_store` class below instead of the naive |
| 14 | +// |
| 15 | +// static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE! |
| 16 | +// |
| 17 | +// which has two serious issues: |
| 18 | +// |
| 19 | +// 1. Py_DECREF() calls potentially after the Python interpreter was finalized already, and |
| 20 | +// 2. deadlocks in multi-threaded processes (because of missing lock ordering). |
| 21 | +// |
| 22 | +// The following alternative avoids both problems: |
| 23 | +// |
| 24 | +// PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage; |
| 25 | +// auto &imported_obj = storage // Do NOT make this `static`! |
| 26 | +// .call_once_and_store_result([]() { |
| 27 | +// return py::module_::import("module_name"); |
| 28 | +// }) |
| 29 | +// .get_stored(); |
| 30 | +// |
| 31 | +// The parameter of `call_once_and_store_result()` must be callable. It can make |
| 32 | +// CPython API calls, and in particular, it can temporarily release the GIL. |
| 33 | +// |
| 34 | +// `T` can be any C++ type, it does not have to involve CPython API types. |
| 35 | +// |
| 36 | +// The behavior with regard to signals, e.g. `SIGINT` (`KeyboardInterrupt`), |
| 37 | +// is not ideal. If the main thread is the one to actually run the `Callable`, |
| 38 | +// then a `KeyboardInterrupt` will interrupt it if it is running normal Python |
| 39 | +// code. The situation is different if a non-main thread runs the |
| 40 | +// `Callable`, and then the main thread starts waiting for it to complete: |
| 41 | +// a `KeyboardInterrupt` will not interrupt the non-main thread, but it will |
| 42 | +// get processed only when it is the main thread's turn again and it is running |
| 43 | +// normal Python code. However, this will be unnoticeable for quick call-once |
| 44 | +// functions, which is usually the case. |
| 45 | +template <typename T> |
| 46 | +class gil_safe_call_once_and_store { |
| 47 | +public: |
| 48 | + // PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called. |
| 49 | + template <typename Callable> |
| 50 | + gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) { |
| 51 | + if (!is_initialized_) { // This read is guarded by the GIL. |
| 52 | + // Multiple threads may enter here, because the GIL is released in the next line and |
| 53 | + // CPython API calls in the `fn()` call below may release and reacquire the GIL. |
| 54 | + gil_scoped_release gil_rel; // Needed to establish lock ordering. |
| 55 | + std::call_once(once_flag_, [&] { |
| 56 | + // Only one thread will ever enter here. |
| 57 | + gil_scoped_acquire gil_acq; |
| 58 | + ::new (storage_) T(fn()); // fn may release, but will reacquire, the GIL. |
| 59 | + is_initialized_ = true; // This write is guarded by the GIL. |
| 60 | + }); |
| 61 | + // All threads will observe `is_initialized_` as true here. |
| 62 | + } |
| 63 | + // Intentionally not returning `T &` to ensure the calling code is self-documenting. |
| 64 | + return *this; |
| 65 | + } |
| 66 | + |
| 67 | + // This must only be called after `call_once_and_store_result()` was called. |
| 68 | + T &get_stored() { |
| 69 | + assert(is_initialized_); |
| 70 | + PYBIND11_WARNING_PUSH |
| 71 | +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5 |
| 72 | + // Needed for gcc 4.8.5 |
| 73 | + PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing") |
| 74 | +#endif |
| 75 | + return *reinterpret_cast<T *>(storage_); |
| 76 | + PYBIND11_WARNING_POP |
| 77 | + } |
| 78 | + |
| 79 | + constexpr gil_safe_call_once_and_store() = default; |
| 80 | + PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default; |
| 81 | + |
| 82 | +private: |
| 83 | + alignas(T) char storage_[sizeof(T)] = {}; |
| 84 | + std::once_flag once_flag_ = {}; |
| 85 | + bool is_initialized_ = false; |
| 86 | + // The `is_initialized_`-`storage_` pair is very similar to `std::optional`, |
| 87 | + // but the latter does not have the triviality properties of former, |
| 88 | + // therefore `std::optional` is not a viable alternative here. |
| 89 | +}; |
| 90 | + |
| 91 | +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) |
0 commit comments