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


gtsam
Author(s):
autogenerated on Wed Mar 19 2025 03:01:44