-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add pybind11/gil_safe_call_once.h (to fix deadlocks in pybind11/numpy.h) #4877
Changes from 3 commits
38317c3
e7b8c4f
74ac0d9
109a165
88cec11
1ce2715
e7be9c2
f07b28b
36be645
7bc16a6
78f4e93
6d9441d
a864f21
6689b06
d965f29
398a42c
ab2cf8e
6d5bdd8
4c5dd1b
8633c5b
82f3efc
3ebd139
c33712d
dcf2b92
4557dce
704fe13
b2f87a8
fad1017
8453302
66bbc67
7cd9390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,14 @@ | |
|
||
#include <algorithm> | ||
#include <array> | ||
#include <cassert> | ||
#include <cstdint> | ||
#include <cstdlib> | ||
#include <cstring> | ||
#include <functional> | ||
#include <numeric> | ||
#include <sstream> | ||
#include <stdalign.h> | ||
#include <string> | ||
#include <type_traits> | ||
#include <typeindex> | ||
|
@@ -42,6 +44,30 @@ class array; // Forward declaration | |
|
||
PYBIND11_NAMESPACE_BEGIN(detail) | ||
|
||
// Main author of this class: jbms@ | ||
template <typename T> | ||
class LazyInitializeAtLeastOnceDestroyNever { | ||
public: | ||
template <typename Initialize> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document that you expect the GIL to be held, or in any case that calls are serialized. (Otherwise the mutating accesses would cause races.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: 1ce2715 |
||
T &Get(Initialize &&initialize) { | ||
if (!initialized_) { | ||
assert(PyGILState_Check()); | ||
// Multiple threads may run this concurrently, but that is fine. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is a bit misleading. Multiple threads may call What is true is that multiple threads may possibly reach the inside of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm uncertain I understand the two points correctly. My understanding: From getting burned in the past, I've developed the strong belief that multiple threads do in fact get EDIT If the first thread calls back into the Python C API (e.g. for a Python import), Python can and does in the general case release the GIL. That gives other threads the opportunity to also pass the But after the Would it help to change the comment to like this?
Fundamentally, the "at least once" aspect here isn't great. "Guaranteed once" would be better. My thinking:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I think when I said "multiple threads can call
I'm not sure how this would happen, and if it did, it'd definitely be a bug, since that's a race.
OK, but that is not "concurrently". Only the thread holding the GIL can be accessing I don't think " There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can say something more concrete and specific to this situation:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I appear to be mixing up terms, which is critical here. What is the correct term? (I want to edit-correct my comment.) E.g.
I want to say what you are saying. Although all I originally wanted: leave a meaningful, terse, and correct comment in the code, just enough to give readers a good clue to follow up on in case they want to fully understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I'm seeing your suggested comment only now. Will adopt. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: 1ce2715 |
||
auto value = initialize(); // May release and re-acquire the GIL. | ||
if (!initialized_) { // This runs with the GIL held, | ||
new // therefore this is reached only once. | ||
(reinterpret_cast<T *>(value_storage_)) T(std::move(value)); | ||
initialized_ = true; | ||
} | ||
} | ||
return *reinterpret_cast<T *>(value_storage_); | ||
} | ||
|
||
private: | ||
alignas(T) char value_storage_[sizeof(T)]; | ||
bool initialized_ = false; | ||
}; | ||
|
||
template <> | ||
struct handle_type_name<array> { | ||
static constexpr auto name = const_name("numpy.ndarray"); | ||
|
@@ -206,8 +232,8 @@ struct npy_api { | |
}; | ||
|
||
static npy_api &get() { | ||
static npy_api api = lookup(); | ||
return api; | ||
static LazyInitializeAtLeastOnceDestroyNever<npy_api> api_init; | ||
return api_init.Get(lookup); | ||
} | ||
|
||
bool PyArray_Check_(PyObject *obj) const { | ||
|
@@ -643,10 +669,11 @@ class dtype : public object { | |
char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } | ||
|
||
private: | ||
static object _dtype_from_pep3118() { | ||
module_ m = detail::import_numpy_core_submodule("_internal"); | ||
static PyObject *obj = m.attr("_dtype_from_pep3118").cast<object>().release().ptr(); | ||
return reinterpret_borrow<object>(obj); | ||
static object &_dtype_from_pep3118() { | ||
static detail::LazyInitializeAtLeastOnceDestroyNever<object> imported_obj; | ||
return imported_obj.Get([]() { | ||
return detail::import_numpy_core_submodule("_internal").attr("_dtype_from_pep3118"); | ||
}); | ||
} | ||
|
||
dtype strip_padding(ssize_t itemsize) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this, this is a C interop header (and we're not writing C).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: 109a165