Skip to content

Commit 39e97e6

Browse files
committed
significant redesign of GIL state handling
1 parent 18fb3e3 commit 39e97e6

File tree

3 files changed

+107
-7
lines changed

3 files changed

+107
-7
lines changed

include/pybind11/cast.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ PYBIND11_NOINLINE inline internals &get_internals() {
5555
internals_ptr = caps;
5656
} else {
5757
internals_ptr = new internals();
58+
#if defined(WITH_THREAD)
59+
PyEval_InitThreads();
60+
PyThreadState *tstate = PyThreadState_Get();
61+
internals_ptr->tstate = PyThread_create_key();
62+
PyThread_set_key_value(internals_ptr->tstate, tstate);
63+
internals_ptr->istate = tstate->interp;
64+
#endif
5865
builtins[id] = capsule(internals_ptr);
5966
}
6067
return *internals_ptr;

include/pybind11/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
#include <Python.h>
4848
#include <frameobject.h>
49+
#include <pythread.h>
4950

5051
#ifdef isalnum
5152
# undef isalnum
@@ -127,6 +128,9 @@
127128
} \
128129
PyObject *pybind11_init()
129130

131+
extern "C" {
132+
extern PyThreadState *_PyThreadState_Current;
133+
};
130134

131135
NAMESPACE_BEGIN(pybind11)
132136

@@ -233,6 +237,10 @@ struct internals {
233237
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
234238
std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject*
235239
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
240+
#if defined(WITH_THREAD)
241+
int tstate = 0;
242+
PyInterpreterState *istate = nullptr;
243+
#endif
236244
};
237245

238246
/// Return a reference to the current 'internals' information

include/pybind11/pybind11.h

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,21 +1039,106 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
10391039
}
10401040

10411041
#if defined(WITH_THREAD)
1042-
inline void init_threading() { PyEval_InitThreads(); }
1042+
1043+
/* The functions below essentially reproduce the PyGILState_* API using a RAII
1044+
* pattern, but there are a few important differences:
1045+
*
1046+
* 1. When acquiring the GIL from an non-main thread during the finalization
1047+
* phase, the GILState API blindly terminates the calling thread, which
1048+
* is often not what is wanted. This API does not do this.
1049+
*
1050+
* 2. The gil_scoped_release function can optionally cut the relationship
1051+
* of a PyThreadState and its associated thread, which allows moving it to
1052+
* another thread (this is a fairly rare/advanced use case).
1053+
*
1054+
* 3. The reference count of an acquired thread state can be controlled. This
1055+
* can be handy to prevent cases where callbacks issued from an external
1056+
* thread constantly construct and destroy thread state data structures. */
10431057

10441058
class gil_scoped_acquire {
1045-
PyGILState_STATE state;
10461059
public:
1047-
inline gil_scoped_acquire() { state = PyGILState_Ensure(); }
1048-
inline ~gil_scoped_acquire() { PyGILState_Release(state); }
1060+
gil_scoped_acquire() {
1061+
auto const &internals = detail::get_internals();
1062+
tstate = (PyThreadState *) PyThread_get_key_value(internals.tstate);
1063+
1064+
if (!tstate) {
1065+
tstate = PyThreadState_New(internals.istate);
1066+
#if !defined(NDEBUG)
1067+
if (!tstate)
1068+
pybind11_fail("scoped_acquire: could not create thread state!");
1069+
#endif
1070+
tstate->gilstate_counter = 0;
1071+
PyThread_set_key_value(internals.tstate, tstate);
1072+
} else {
1073+
release = _PyThreadState_Current != tstate;
1074+
}
1075+
1076+
if (release) {
1077+
PyInterpreterState *interp = tstate->interp;
1078+
/* Work around an annoying assertion in PyThreadState_Swap */
1079+
tstate->interp = nullptr;
1080+
PyEval_AcquireThread(tstate);
1081+
tstate->interp = interp;
1082+
}
1083+
1084+
inc_ref();
1085+
}
1086+
1087+
void inc_ref() {
1088+
++tstate->gilstate_counter;
1089+
}
1090+
1091+
void dec_ref() {
1092+
--tstate->gilstate_counter;
1093+
#if !defined(NDEBUG)
1094+
if (_PyThreadState_Current != tstate)
1095+
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
1096+
if (tstate->gilstate_counter < 0)
1097+
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
1098+
#endif
1099+
if (tstate->gilstate_counter == 0) {
1100+
#if !defined(NDEBUG)
1101+
if (!release)
1102+
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
1103+
#endif
1104+
PyThreadState_Clear(tstate);
1105+
PyThreadState_DeleteCurrent();
1106+
PyThread_set_key_value(detail::get_internals().tstate, nullptr);
1107+
release = false;
1108+
}
1109+
}
1110+
1111+
~gil_scoped_acquire() {
1112+
dec_ref();
1113+
if (release)
1114+
PyEval_SaveThread();
1115+
}
1116+
private:
1117+
PyThreadState *tstate = nullptr;
1118+
bool release = true;
10491119
};
10501120

10511121
class gil_scoped_release {
1052-
PyThreadState *state;
10531122
public:
1054-
inline gil_scoped_release() { state = PyEval_SaveThread(); }
1055-
inline ~gil_scoped_release() { PyEval_RestoreThread(state); }
1123+
gil_scoped_release(bool disassoc = false) : disassoc(disassoc) {
1124+
tstate = PyEval_SaveThread();
1125+
if (disassoc)
1126+
PyThread_set_key_value(detail::get_internals().tstate, nullptr);
1127+
}
1128+
~gil_scoped_release() {
1129+
if (!tstate)
1130+
return;
1131+
PyEval_RestoreThread(tstate);
1132+
if (disassoc)
1133+
PyThread_set_key_value(detail::get_internals().tstate, tstate);
1134+
}
1135+
private:
1136+
PyThreadState *tstate;
1137+
bool disassoc;
10561138
};
1139+
#else
1140+
class gil_scoped_acquire { };
1141+
class gil_scoped_release { };
10571142
#endif
10581143

10591144
inline function get_overload(const void *this_ptr, const char *name) {

0 commit comments

Comments
 (0)