88#include < cassert>
99#include < mutex>
1010
11- #ifdef Py_GIL_DISABLED
11+ #if defined( Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
1212# include < atomic>
13+
14+ using atomic_bool = std::atomic_bool;
15+ #else
16+ using atomic_bool = bool ;
1317#endif
1418
1519PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE)
@@ -53,16 +57,18 @@ class gil_safe_call_once_and_store {
5357public:
5458 // PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
5559 template <typename Callable>
56- gil_safe_call_once_and_store &call_once_and_store_result (Callable &&fn) {
60+ gil_safe_call_once_and_store &call_once_and_store_result (Callable &&fn,
61+ void (*finalize_fn)(T &) = nullptr ) {
5762 if (!is_initialized_) { // This read is guarded by the GIL.
5863 // Multiple threads may enter here, because the GIL is released in the next line and
5964 // CPython API calls in the `fn()` call below may release and reacquire the GIL.
6065 gil_scoped_release gil_rel; // Needed to establish lock ordering.
6166 std::call_once (once_flag_, [&] {
6267 // Only one thread will ever enter here.
6368 gil_scoped_acquire gil_acq;
64- ::new (storage_) T (fn ()); // fn may release, but will reacquire, the GIL.
65- is_initialized_ = true ; // This write is guarded by the GIL.
69+ ::new (storage_) T (fn ()); // fn may release, but will reacquire, the GIL.
70+ finalize_fn_ = finalize_fn; // Store the finalizer.
71+ is_initialized_ = true ; // This write is guarded by the GIL.
6672 });
6773 // All threads will observe `is_initialized_` as true here.
6874 }
@@ -83,20 +89,21 @@ class gil_safe_call_once_and_store {
8389 }
8490
8591 constexpr gil_safe_call_once_and_store () = default ;
86- PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store () = default ;
92+ PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store () {
93+ if (is_initialized_ && finalize_fn_ != nullptr ) {
94+ finalize_fn_ (*reinterpret_cast <T *>(storage_));
95+ }
96+ }
8797
8898private:
8999 alignas (T) char storage_[sizeof (T)] = {};
90100 std::once_flag once_flag_;
91- #ifdef Py_GIL_DISABLED
92- std::atomic_bool
93- #else
94- bool
95- #endif
96- is_initialized_{false };
101+ void (*finalize_fn_)(T &) = nullptr ;
102+
97103 // The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
98104 // but the latter does not have the triviality properties of former,
99105 // therefore `std::optional` is not a viable alternative here.
106+ atomic_bool is_initialized_{false };
100107};
101108
102109PYBIND11_NAMESPACE_END (PYBIND11_NAMESPACE)
0 commit comments