Skip to content

Commit 3c8fce6

Browse files
chgnrdvserhiy-storchaka
authored andcommitted
pythongh-109746: Make _thread.start_new_thread delete state of new thread on its startup failure (pythonGH-109761)
If Python fails to start newly created thread due to failure of underlying PyThread_start_new_thread() call, its state should be removed from interpreter' thread states list to avoid its double cleanup. Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent e7a9d8d commit 3c8fce6

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

Lib/test/test_threading.py

+34
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,40 @@ def __del__(self):
11711171
self.assertEqual(out.strip(), b"OK")
11721172
self.assertIn(b"can't create new thread at interpreter shutdown", err)
11731173

1174+
def test_start_new_thread_failed(self):
1175+
# gh-109746: if Python fails to start newly created thread
1176+
# due to failure of underlying PyThread_start_new_thread() call,
1177+
# its state should be removed from interpreter' thread states list
1178+
# to avoid its double cleanup
1179+
try:
1180+
from resource import setrlimit, RLIMIT_NPROC
1181+
except ImportError as err:
1182+
self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD
1183+
code = """if 1:
1184+
import resource
1185+
import _thread
1186+
1187+
def f():
1188+
print("shouldn't be printed")
1189+
1190+
limits = resource.getrlimit(resource.RLIMIT_NPROC)
1191+
[_, hard] = limits
1192+
resource.setrlimit(resource.RLIMIT_NPROC, (0, hard))
1193+
1194+
try:
1195+
_thread.start_new_thread(f, ())
1196+
except RuntimeError:
1197+
print('ok')
1198+
else:
1199+
print('skip')
1200+
"""
1201+
_, out, err = assert_python_ok("-u", "-c", code)
1202+
out = out.strip()
1203+
if out == b'skip':
1204+
self.skipTest('RLIMIT_NPROC had no effect; probably superuser')
1205+
self.assertEqual(out, b'ok')
1206+
self.assertEqual(err, b'')
1207+
11741208
@cpython_only
11751209
def test_finalize_daemon_thread_hang(self):
11761210
if support.check_sanitizer(thread=True, memory=True):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
If :func:`!_thread.start_new_thread` fails to start a new thread, it deletes its state from interpreter and thus avoids its repeated cleanup on finalization.

Modules/_threadmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ ThreadHandle_start(ThreadHandle *self, PyObject *func, PyObject *args,
421421
PyThread_handle_t os_handle;
422422
if (PyThread_start_joinable_thread(thread_run, boot, &ident, &os_handle)) {
423423
PyThreadState_Clear(boot->tstate);
424+
PyThreadState_Delete(boot->tstate);
424425
thread_bootstate_free(boot, 1);
425426
PyErr_SetString(ThreadError, "can't start new thread");
426427
goto start_failed;

Python/pystate.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -1779,7 +1779,9 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
17791779
if (tstate->_status.bound_gilstate) {
17801780
unbind_gilstate_tstate(tstate);
17811781
}
1782-
unbind_tstate(tstate);
1782+
if (tstate->_status.bound) {
1783+
unbind_tstate(tstate);
1784+
}
17831785

17841786
// XXX Move to PyThreadState_Clear()?
17851787
clear_datastack(tstate);

0 commit comments

Comments
 (0)