Skip to content

Commit 58834ac

Browse files
committed
Make ~gil_safe_call_once_and_store a no-op
1 parent d2b7605 commit 58834ac

File tree

1 file changed

+21
-35
lines changed

1 file changed

+21
-35
lines changed

include/pybind11/gil_safe_call_once.h

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class gil_safe_call_once_and_store {
6060
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
6161
template <typename Callable>
6262
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
63-
void (*finalize_fn)(T &) = nullptr) {
63+
void (*finalize_fn)(T &) /*unused*/) {
6464

6565
if (!is_initialized_) { // This read is guarded by the GIL.
6666
// Multiple threads may enter here, because the GIL is released in the next line and
@@ -69,9 +69,8 @@ class gil_safe_call_once_and_store {
6969
std::call_once(once_flag_, [&] {
7070
// Only one thread will ever enter here.
7171
gil_scoped_acquire gil_acq;
72-
::new (storage_) T(fn()); // fn may release, but will reacquire, the GIL.
73-
finalize_fn_ = finalize_fn; // Store the finalizer.
74-
is_initialized_ = true; // This write is guarded by the GIL.
72+
::new (storage_) T(fn()); // fn may release, but will reacquire, the GIL.
73+
is_initialized_ = true; // This write is guarded by the GIL.
7574
});
7675
// All threads will observe `is_initialized_` as true here.
7776
}
@@ -92,17 +91,15 @@ class gil_safe_call_once_and_store {
9291
}
9392

9493
constexpr gil_safe_call_once_and_store() = default;
95-
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() {
96-
if (is_initialized_ && finalize_fn_ != nullptr) {
97-
finalize_fn_(*reinterpret_cast<T *>(storage_));
98-
}
99-
}
94+
// The instance is a global static, so its destructor runs when the process
95+
// is terminating. Therefore, do nothing here because the Python interpreter
96+
// may have been finalized already.
97+
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
10098

10199
private:
102100
// Global static storage (per process) when subinterpreter support is disabled.
103101
alignas(T) char storage_[sizeof(T)] = {};
104102
std::once_flag once_flag_;
105-
void (*finalize_fn_)(T &) = nullptr;
106103

107104
// The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
108105
// but the latter does not have the triviality properties of former,
@@ -124,19 +121,19 @@ class gil_safe_call_once_and_store {
124121
if (!is_initialized_by_atleast_one_interpreter_
125122
|| detail::get_num_interpreters_seen() > 1) {
126123
detail::with_internals([&](detail::internals &internals) {
127-
const void *key = reinterpret_cast<const void *>(this);
124+
const void *k = reinterpret_cast<const void *>(this);
128125
auto &storage_map = internals.call_once_storage_map;
129-
auto it = storage_map.find(key);
126+
auto it = storage_map.find(k);
130127
if (it == storage_map.end()) {
131128
gil_scoped_release gil_rel; // Needed to establish lock ordering.
132129
{
133130
// Only one thread will ever enter here.
134131
gil_scoped_acquire gil_acq;
135-
auto s = new detail::call_once_storage<T>{};
136-
::new (s->storage) T(fn()); // fn may release, but will reacquire, the GIL.
137-
s->finalize = finalize_fn;
138-
last_storage_ = reinterpret_cast<T *>(s->storage);
139-
storage_map.emplace(key, s);
132+
auto v = new detail::call_once_storage<T>{};
133+
::new (v->storage) T(fn()); // fn may release, but will reacquire, the GIL.
134+
v->finalize = finalize_fn;
135+
last_storage_ = reinterpret_cast<T *>(v->storage);
136+
storage_map.emplace(k, v);
140137
};
141138
}
142139
is_initialized_by_atleast_one_interpreter_ = true;
@@ -153,32 +150,21 @@ class gil_safe_call_once_and_store {
153150
if (!is_initialized_by_atleast_one_interpreter_
154151
|| detail::get_num_interpreters_seen() > 1) {
155152
detail::with_internals([&](detail::internals &internals) {
156-
const void *key = reinterpret_cast<const void *>(this);
153+
const void *k = reinterpret_cast<const void *>(this);
157154
auto &storage_map = internals.call_once_storage_map;
158-
auto it = storage_map.find(key);
159-
assert(it != storage_map.end());
160-
auto *s = static_cast<detail::call_once_storage<T> *>(it->second);
161-
result = last_storage_ = reinterpret_cast<T *>(s->storage);
155+
auto *v = static_cast<detail::call_once_storage<T> *>(storage_map.at(k));
156+
result = last_storage_ = reinterpret_cast<T *>(v->storage);
162157
});
163158
}
164159
assert(result != nullptr);
165160
return *result;
166161
}
167162

168163
constexpr gil_safe_call_once_and_store() = default;
169-
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() {
170-
if (is_initialized_by_atleast_one_interpreter_) {
171-
detail::with_internals([&](detail::internals &internals) {
172-
const void *key = reinterpret_cast<const void *>(this);
173-
auto &storage_map = internals.call_once_storage_map;
174-
auto it = storage_map.find(key);
175-
if (it != storage_map.end()) {
176-
delete it->second;
177-
storage_map.erase(it);
178-
}
179-
});
180-
}
181-
}
164+
// The instance is a global static, so its destructor runs when the process
165+
// is terminating. Therefore, do nothing here because the Python interpreter
166+
// may have been finalized already.
167+
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
182168

183169
private:
184170
// No storage needed when subinterpreter support is enabled.

0 commit comments

Comments
 (0)