Skip to content

Commit f1e7424

Browse files
[3.12] gh-109746: Make _thread.start_new_thread delete state of new thread on its startup failure (GH-109761) (GH-127173)
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. (cherry picked from commit ca3ea9a) Co-authored-by: Radislav Chugunov <[email protected]>
1 parent 2964898 commit f1e7424

File tree

4 files changed

+40
-1
lines changed

4 files changed

+40
-1
lines changed

Lib/test/test_threading.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,41 @@ def __del__(self):
11501150
self.assertEqual(out.strip(), b"OK")
11511151
self.assertIn(b"can't create new thread at interpreter shutdown", err)
11521152

1153+
def test_start_new_thread_failed(self):
1154+
# gh-109746: if Python fails to start newly created thread
1155+
# due to failure of underlying PyThread_start_new_thread() call,
1156+
# its state should be removed from interpreter' thread states list
1157+
# to avoid its double cleanup
1158+
try:
1159+
from resource import setrlimit, RLIMIT_NPROC
1160+
except ImportError as err:
1161+
self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD
1162+
code = """if 1:
1163+
import resource
1164+
import _thread
1165+
1166+
def f():
1167+
print("shouldn't be printed")
1168+
1169+
limits = resource.getrlimit(resource.RLIMIT_NPROC)
1170+
[_, hard] = limits
1171+
resource.setrlimit(resource.RLIMIT_NPROC, (0, hard))
1172+
1173+
try:
1174+
_thread.start_new_thread(f, ())
1175+
except RuntimeError:
1176+
print('ok')
1177+
else:
1178+
print('skip')
1179+
"""
1180+
_, out, err = assert_python_ok("-u", "-c", code)
1181+
out = out.strip()
1182+
if out == b'skip':
1183+
self.skipTest('RLIMIT_NPROC had no effect; probably superuser')
1184+
self.assertEqual(out, b'ok')
1185+
self.assertEqual(err, b'')
1186+
1187+
11531188
class ThreadJoinOnShutdown(BaseTestCase):
11541189

11551190
def _run_and_join(self, script):
Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
12191219
if (ident == PYTHREAD_INVALID_THREAD_ID) {
12201220
PyErr_SetString(ThreadError, "can't start new thread");
12211221
PyThreadState_Clear(boot->tstate);
1222+
PyThreadState_Delete(boot->tstate);
12221223
thread_bootstate_free(boot, 1);
12231224
return NULL;
12241225
}

Python/pystate.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,9 @@ tstate_delete_common(PyThreadState *tstate)
15931593
if (tstate->_status.bound_gilstate) {
15941594
unbind_gilstate_tstate(tstate);
15951595
}
1596-
unbind_tstate(tstate);
1596+
if (tstate->_status.bound) {
1597+
unbind_tstate(tstate);
1598+
}
15971599

15981600
// XXX Move to PyThreadState_Clear()?
15991601
clear_datastack(tstate);

0 commit comments

Comments
 (0)