Skip to content

Commit 79cb013

Browse files
malfethenryiii
andauthored
fix: allow users to avoid thread termination in scoped_released (#2657)
* Avoid thread termination in scoped_released Do not call `PyEval_RestoreThread()` from `~gil_scoped_release()` if python runtime is finalizing, as it will result in thread termination in Python runtime newer than 3.6, as documented in https://docs.python.org/3/c-api/init.html#c.PyEval_RestoreThread Similarly do not call `PyThreadState_DeleteCurrent` from `~gil_scoped_acquire()` if runtime is finalizing. Discovered while debugging PyTorch crash using Python-3.9 described in pytorch/pytorch#47776 * Simplify _Py_IsFinalizing() availability check * Fix typo * Add version agnostic `detail::finalization_guard()` * Move `finalization_guard` to detail/common.h And rename it to `is_finalizing` * Move `is_finalizing()` back to pybind11.h * Simplify `is_finalizing()` check One should follow documentation rather than make any assumptions * feat: disarm * docs: fix comment Co-authored-by: Henry Schreiner <[email protected]>
1 parent cecdfad commit 79cb013

File tree

1 file changed

+34
-4
lines changed

1 file changed

+34
-4
lines changed

include/pybind11/pybind11.h

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2113,12 +2113,22 @@ class gil_scoped_acquire {
21132113
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
21142114
#endif
21152115
PyThreadState_Clear(tstate);
2116-
PyThreadState_DeleteCurrent();
2116+
if (active)
2117+
PyThreadState_DeleteCurrent();
21172118
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
21182119
release = false;
21192120
}
21202121
}
21212122

2123+
/// This method will disable the PyThreadState_DeleteCurrent call and the
2124+
/// GIL won't be acquired. This method should be used if the interpreter
2125+
/// could be shutting down when this is called, as thread deletion is not
2126+
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
2127+
/// protect subsequent code.
2128+
PYBIND11_NOINLINE void disarm() {
2129+
active = false;
2130+
}
2131+
21222132
PYBIND11_NOINLINE ~gil_scoped_acquire() {
21232133
dec_ref();
21242134
if (release)
@@ -2127,6 +2137,7 @@ class gil_scoped_acquire {
21272137
private:
21282138
PyThreadState *tstate = nullptr;
21292139
bool release = true;
2140+
bool active = true;
21302141
};
21312142

21322143
class gil_scoped_release {
@@ -2142,10 +2153,22 @@ class gil_scoped_release {
21422153
PYBIND11_TLS_DELETE_VALUE(key);
21432154
}
21442155
}
2156+
2157+
/// This method will disable the PyThreadState_DeleteCurrent call and the
2158+
/// GIL won't be acquired. This method should be used if the interpreter
2159+
/// could be shutting down when this is called, as thread deletion is not
2160+
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
2161+
/// protect subsequent code.
2162+
PYBIND11_NOINLINE void disarm() {
2163+
active = false;
2164+
}
2165+
21452166
~gil_scoped_release() {
21462167
if (!tstate)
21472168
return;
2148-
PyEval_RestoreThread(tstate);
2169+
// `PyEval_RestoreThread()` should not be called if runtime is finalizing
2170+
if (active)
2171+
PyEval_RestoreThread(tstate);
21492172
if (disassoc) {
21502173
auto key = detail::get_internals().tstate;
21512174
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
@@ -2154,24 +2177,31 @@ class gil_scoped_release {
21542177
private:
21552178
PyThreadState *tstate;
21562179
bool disassoc;
2180+
bool active = true;
21572181
};
21582182
#elif defined(PYPY_VERSION)
21592183
class gil_scoped_acquire {
21602184
PyGILState_STATE state;
21612185
public:
21622186
gil_scoped_acquire() { state = PyGILState_Ensure(); }
21632187
~gil_scoped_acquire() { PyGILState_Release(state); }
2188+
void disarm() {}
21642189
};
21652190

21662191
class gil_scoped_release {
21672192
PyThreadState *state;
21682193
public:
21692194
gil_scoped_release() { state = PyEval_SaveThread(); }
21702195
~gil_scoped_release() { PyEval_RestoreThread(state); }
2196+
void disarm() {}
21712197
};
21722198
#else
2173-
class gil_scoped_acquire { };
2174-
class gil_scoped_release { };
2199+
class gil_scoped_acquire {
2200+
void disarm() {}
2201+
};
2202+
class gil_scoped_release {
2203+
void disarm() {}
2204+
};
21752205
#endif
21762206

21772207
error_already_set::~error_already_set() {

0 commit comments

Comments
 (0)