Skip to content

Commit d9daef5

Browse files
committed
Apply suggestions from code review
1 parent e84e9c1 commit d9daef5

File tree

1 file changed

+28
-21
lines changed

1 file changed

+28
-21
lines changed

include/pybind11/gil_safe_call_once.h

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919

2020
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
2121

22-
namespace detail {
22+
PYBIND11_NAMESPACE_BEGIN(detail)
2323
#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
2424
using atomic_bool = std::atomic_bool;
2525
#else
2626
using atomic_bool = bool;
2727
#endif
28-
} // namespace detail
28+
PYBIND11_NAMESPACE_END(detail)
2929

3030
// Use the `gil_safe_call_once_and_store` class below instead of the naive
3131
//
@@ -127,6 +127,7 @@ class gil_safe_call_once_and_store {
127127
// subinterpreter has its own separate state. The cached result may not shareable across
128128
// interpreters (e.g., imported modules and their members).
129129

130+
PYBIND11_NAMESPACE_BEGIN(detail)
130131
struct call_once_storage_base {
131132
call_once_storage_base() = default;
132133
virtual ~call_once_storage_base() = default;
@@ -159,9 +160,7 @@ struct call_once_storage : call_once_storage_base {
159160
call_once_storage &operator=(call_once_storage &&) = delete;
160161
};
161162

162-
/// Storage map for `gil_safe_call_once_and_store`. Stored in a capsule in the interpreter's state
163-
/// dict with proper destructor to ensure cleanup when the interpreter is destroyed.
164-
using call_once_storage_map_type = std::unordered_map<const void *, call_once_storage_base *>;
163+
PYBIND11_NAMESPACE_END(detail)
165164

166165
# define PYBIND11_CALL_ONCE_STORAGE_MAP_ID PYBIND11_INTERNALS_ID "_call_once_storage_map__"
167166

@@ -178,18 +177,18 @@ class gil_safe_call_once_and_store {
178177
// Multiple threads may enter here, because the GIL is released in the next line and
179178
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
180179
gil_scoped_release gil_rel; // Needed to establish lock ordering.
181-
const void *const key = reinterpret_cast<const void *>(this);
180+
const void *const key = this;
182181
// There can be multiple threads going through here.
183-
call_once_storage<T> *value = nullptr;
182+
storage_type *value = nullptr;
184183
{
185184
gil_scoped_acquire gil_acq;
186185
// Only one thread will enter here at a time.
187186
auto &storage_map = *get_or_create_call_once_storage_map();
188187
const auto it = storage_map.find(key);
189188
if (it != storage_map.end()) {
190-
value = static_cast<call_once_storage<T> *>(it->second);
189+
value = static_cast<storage_type *>(it->second);
191190
} else {
192-
value = new call_once_storage<T>{};
191+
value = new storage_type{};
193192
storage_map.emplace(key, value);
194193
}
195194
}
@@ -215,9 +214,9 @@ class gil_safe_call_once_and_store {
215214
T *result = last_storage_ptr_;
216215
if (!is_last_storage_valid()) {
217216
gil_scoped_acquire gil_acq;
218-
const void *const key = reinterpret_cast<const void *>(this);
217+
const void *const key = this;
219218
auto &storage_map = *get_or_create_call_once_storage_map();
220-
auto *value = static_cast<call_once_storage<T> *>(storage_map.at(key));
219+
auto *value = static_cast<storage_type *>(storage_map.at(key));
221220
result = last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
222221
}
223222
assert(result != nullptr);
@@ -231,20 +230,27 @@ class gil_safe_call_once_and_store {
231230
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
232231

233232
private:
233+
using storage_base_type = detail::call_once_storage_base;
234+
using storage_type = detail::call_once_storage<T>;
235+
// Use base type pointer for polymorphism
236+
using storage_map_type = std::unordered_map<const void *, storage_base_type *>;
237+
234238
bool is_last_storage_valid() const {
235239
return is_initialized_by_at_least_one_interpreter_
236240
&& detail::get_num_interpreters_seen() == 1;
237241
}
238242

239-
static call_once_storage_map_type *get_or_create_call_once_storage_map() {
243+
// Storage map for `gil_safe_call_once_and_store`. Stored in a capsule in the interpreter's
244+
// state dict with proper destructor to ensure cleanup when the interpreter is destroyed.
245+
static storage_map_type *get_or_create_call_once_storage_map() {
240246
// Preserve any existing Python error state. dict_getitemstringref may clear
241247
// errors or set new ones when the key is not found; we restore the original
242248
// error state when this scope exits.
243249
error_scope err_scope;
244-
dict state_dict = detail::get_python_state_dict();
250+
auto state_dict = reinterpret_borrow<dict>(detail::get_python_state_dict());
245251
auto storage_map_obj = reinterpret_steal<object>(
246252
detail::dict_getitemstringref(state_dict.ptr(), PYBIND11_CALL_ONCE_STORAGE_MAP_ID));
247-
call_once_storage_map_type *storage_map = nullptr;
253+
storage_map_type *storage_map = nullptr;
248254
if (storage_map_obj) {
249255
void *raw_ptr = PyCapsule_GetPointer(storage_map_obj.ptr(), /*name=*/nullptr);
250256
if (!raw_ptr) {
@@ -253,21 +259,22 @@ class gil_safe_call_once_and_store {
253259
"get_or_create_call_once_storage_map() FAILED");
254260
throw error_already_set();
255261
}
256-
storage_map = reinterpret_cast<call_once_storage_map_type *>(raw_ptr);
262+
storage_map = static_cast<storage_map_type *>(raw_ptr);
257263
} else {
258264
// Use unique_ptr for exception safety: if capsule creation throws,
259265
// the map is automatically deleted.
260-
auto storage_map_ptr
261-
= std::unique_ptr<call_once_storage_map_type>(new call_once_storage_map_type());
266+
auto storage_map_ptr = std::unique_ptr<storage_map_type>(new storage_map_type());
262267
// Create capsule with destructor to clean up the storage map when the interpreter
263268
// shuts down
264269
state_dict[PYBIND11_CALL_ONCE_STORAGE_MAP_ID]
265270
= capsule(storage_map_ptr.get(), [](void *ptr) noexcept {
266-
auto *map = reinterpret_cast<call_once_storage_map_type *>(ptr);
267-
for (const auto &entry : *map) {
268-
delete entry.second;
271+
auto *map = static_cast<storage_map_type *>(ptr);
272+
while (!map->empty()) {
273+
auto it = map->begin();
274+
const auto *storage_ptr = it->second;
275+
map->erase(it);
276+
delete storage_ptr;
269277
}
270-
delete map;
271278
});
272279
// Capsule now owns the storage map, release from unique_ptr
273280
storage_map = storage_map_ptr.release();

0 commit comments

Comments
 (0)