Skip to content

Commit

Permalink
Make the API more self-documenting (and possibly more easily reusable).
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralf W. Grosse-Kunstleve committed Oct 9, 2023
1 parent 6d5bdd8 commit 4c5dd1b
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 12 deletions.
22 changes: 16 additions & 6 deletions include/pybind11/gil_safe_call_once.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
//
// The following alternative avoids both problems:
//
// PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> obj_importer;
// auto imported_obj = obj_importer.get_stored([]() { return py::module_::import("module_name");
// });
// PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
// auto &imported_obj = storage // Do NOT make this `static`!
// .call_once_and_store_result([]() {
// return py::module_::import("module_name");
// })
// .get_stored();
//
// The `get_stored()` argument is meant to be a callable that makes Python C API calls.
// The `call_once_and_store_result()` argument is meant to be a callable that
// makes Python C API calls.
//
// `T` can be any C++ type, it does not have to be a Python type.
template <typename T>
class gil_safe_call_once_and_store {
public:
// PRECONDITION: The GIL must be held when `get_stored()` is called.
template <typename Callable>
T &get_stored(Callable &&fn) {
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
if (!is_initialized_.load(std::memory_order_acquire)) {
gil_scoped_release gil_rel;
std::call_once(once_flag_, [&] {
Expand All @@ -44,6 +48,12 @@ class gil_safe_call_once_and_store {
is_initialized_.store(true, std::memory_order_release);
});
}
return *this;
}

// This must only be called after `call_once_and_store()` was called.
T &get_stored() const {
assert(is_initialized_.load(std::memory_order_relaxed));
PYBIND11_WARNING_PUSH
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
// Needed for gcc 4.8.5
Expand All @@ -57,7 +67,7 @@ class gil_safe_call_once_and_store {
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;

private:
alignas(T) char storage_[sizeof(T)] = {};
alignas(T) mutable char storage_[sizeof(T)] = {};
std::once_flag once_flag_ = {};
std::atomic<bool> is_initialized_ = {};
};
Expand Down
15 changes: 9 additions & 6 deletions include/pybind11/numpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ struct npy_api {
};

static npy_api &get() {
PYBIND11_CONSTINIT static gil_safe_call_once_and_store<npy_api> imported_api;
return imported_api.get_stored(lookup);
PYBIND11_CONSTINIT static gil_safe_call_once_and_store<npy_api> storage;
return storage.call_once_and_store_result(lookup).get_stored();
}

bool PyArray_Check_(PyObject *obj) const {
Expand Down Expand Up @@ -645,10 +645,13 @@ class dtype : public object {

private:
static object &_dtype_from_pep3118() {
PYBIND11_CONSTINIT static gil_safe_call_once_and_store<object> imported_obj;
return imported_obj.get_stored([]() {
return detail::import_numpy_core_submodule("_internal").attr("_dtype_from_pep3118");
});
PYBIND11_CONSTINIT static gil_safe_call_once_and_store<object> storage;
return storage
.call_once_and_store_result([]() {
return detail::import_numpy_core_submodule("_internal")
.attr("_dtype_from_pep3118");
})
.get_stored();
}

dtype strip_padding(ssize_t itemsize) {
Expand Down

0 comments on commit 4c5dd1b

Please sign in to comment.