@@ -1039,21 +1039,106 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
1039
1039
}
1040
1040
1041
1041
#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. */
1043
1057
1044
1058
class gil_scoped_acquire {
1045
- PyGILState_STATE state;
1046
1059
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 ;
1049
1119
};
1050
1120
1051
1121
class gil_scoped_release {
1052
- PyThreadState *state;
1053
1122
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;
1056
1138
};
1139
+ #else
1140
+ class gil_scoped_acquire { };
1141
+ class gil_scoped_release { };
1057
1142
#endif
1058
1143
1059
1144
inline function get_overload (const void *this_ptr, const char *name) {
0 commit comments