Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit82e4824

Browse files
encukoutaegyunkim
authored andcommitted
pythongh-87135: threading.Lock: Raise rather than hang on Python finalization (pythonGH-135991)
After Python finalization gets to the point where no other threadcan attach thread state, attempting to acquire a Python lock must hang.Raise PythonFinalizationError instead of hanging.
1 parentcbdf818 commit82e4824

File tree

6 files changed

+97
-5
lines changed

6 files changed

+97
-5
lines changed

‎Doc/library/exceptions.rst‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,9 @@ The following exceptions are the exceptions that are usually raised.
429429

430430
* Creating a new Python thread.
431431
*:meth:`Joining <threading.Thread.join>` a running daemon thread.
432-
*:func:`os.fork`.
432+
*:func:`os.fork`,
433+
* acquiring a lock such as:class:`threading.Lock`, when it is known that
434+
the operation would otherwise deadlock.
433435

434436
See also the:func:`sys.is_finalizing` function.
435437

@@ -440,6 +442,11 @@ The following exceptions are the exceptions that are usually raised.
440442

441443
:meth:`threading.Thread.join` can now raise this exception.
442444

445+
..versionchanged::next
446+
447+
This exception may be raised when acquiring:meth:`threading.Lock`
448+
or:meth:`threading.RLock`.
449+
443450
..exception::RecursionError
444451

445452
This exception is derived from:exc:`RuntimeError`. It is raised when the

‎Include/internal/pycore_lock.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ typedef enum _PyLockFlags {
5151

5252
// Fail if interrupted by a signal while waiting on the lock.
5353
_PY_FAIL_IF_INTERRUPTED =4,
54+
55+
// Locking & unlocking this lock requires attached thread state.
56+
// If locking returns PY_LOCK_FAILURE, a Python exception *may* be raised.
57+
// (Intended for use with _PY_LOCK_HANDLE_SIGNALS and _PY_LOCK_DETACH.)
58+
_PY_LOCK_PYTHONLOCK =8,
5459
} _PyLockFlags;
5560

5661
// Lock a mutex with an optional timeout and additional options. See

‎Lib/test/test_threading.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,61 @@ def __del__(self):
12471247
self.assertEqual(err,b"")
12481248
self.assertIn(b"all clear",out)
12491249

1250+
@support.subTests('lock_class_name', ['Lock','RLock'])
1251+
deftest_acquire_daemon_thread_lock_in_finalization(self,lock_class_name):
1252+
# gh-123940: Py_Finalize() prevents other threads from running Python
1253+
# code (and so, releasing locks), so acquiring a locked lock can not
1254+
# succeed.
1255+
# We raise an exception rather than hang.
1256+
code=textwrap.dedent(f"""
1257+
import threading
1258+
import time
1259+
1260+
thread_started_event = threading.Event()
1261+
1262+
lock = threading.{lock_class_name}()
1263+
def loop():
1264+
if{lock_class_name!r} == 'RLock':
1265+
lock.acquire()
1266+
with lock:
1267+
thread_started_event.set()
1268+
while True:
1269+
time.sleep(1)
1270+
1271+
uncontested_lock = threading.{lock_class_name}()
1272+
1273+
class Cycle:
1274+
def __init__(self):
1275+
self.self_ref = self
1276+
self.thr = threading.Thread(
1277+
target=loop, daemon=True)
1278+
self.thr.start()
1279+
thread_started_event.wait()
1280+
1281+
def __del__(self):
1282+
assert self.thr.is_alive()
1283+
1284+
# We *can* acquire an unlocked lock
1285+
uncontested_lock.acquire()
1286+
if{lock_class_name!r} == 'RLock':
1287+
uncontested_lock.acquire()
1288+
1289+
# Acquiring a locked one fails
1290+
try:
1291+
lock.acquire()
1292+
except PythonFinalizationError:
1293+
assert self.thr.is_alive()
1294+
print('got the correct exception!')
1295+
1296+
# Cycle holds a reference to itself, which ensures it is
1297+
# cleaned up during the GC that runs after daemon threads
1298+
# have been forced to exit during finalization.
1299+
Cycle()
1300+
""")
1301+
rc,out,err=assert_python_ok("-c",code)
1302+
self.assertEqual(err,b"")
1303+
self.assertIn(b"got the correct exception",out)
1304+
12501305
deftest_start_new_thread_failed(self):
12511306
# gh-109746: if Python fails to start newly created thread
12521307
# due to failure of underlying PyThread_start_new_thread() call,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Acquiring a:class:`threading.Lock` or:class:`threading.RLock` at interpreter
2+
shutdown will raise:exc:`PythonFinalizationError` if Python can determine
3+
that it would otherwise deadlock.

‎Modules/_threadmodule.c‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,14 @@ lock_PyThread_acquire_lock(PyObject *op, PyObject *args, PyObject *kwds)
834834
returnNULL;
835835
}
836836

837-
PyLockStatusr=_PyMutex_LockTimed(&self->lock,timeout,
838-
_PY_LOCK_HANDLE_SIGNALS |_PY_LOCK_DETACH);
837+
PyLockStatusr=_PyMutex_LockTimed(
838+
&self->lock,timeout,
839+
_PY_LOCK_PYTHONLOCK |_PY_LOCK_HANDLE_SIGNALS |_PY_LOCK_DETACH);
839840
if (r==PY_LOCK_INTR) {
841+
assert(PyErr_Occurred());
842+
returnNULL;
843+
}
844+
if (r==PY_LOCK_FAILURE&&PyErr_Occurred()) {
840845
returnNULL;
841846
}
842847

@@ -1054,9 +1059,14 @@ rlock_acquire(PyObject *op, PyObject *args, PyObject *kwds)
10541059
returnNULL;
10551060
}
10561061

1057-
PyLockStatusr=_PyRecursiveMutex_LockTimed(&self->lock,timeout,
1058-
_PY_LOCK_HANDLE_SIGNALS |_PY_LOCK_DETACH);
1062+
PyLockStatusr=_PyRecursiveMutex_LockTimed(
1063+
&self->lock,timeout,
1064+
_PY_LOCK_PYTHONLOCK |_PY_LOCK_HANDLE_SIGNALS |_PY_LOCK_DETACH);
10591065
if (r==PY_LOCK_INTR) {
1066+
assert(PyErr_Occurred());
1067+
returnNULL;
1068+
}
1069+
if (r==PY_LOCK_FAILURE&&PyErr_Occurred()) {
10601070
returnNULL;
10611071
}
10621072

‎Python/lock.c‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
9595
if (timeout==0) {
9696
returnPY_LOCK_FAILURE;
9797
}
98+
if ((flags&_PY_LOCK_PYTHONLOCK)&&Py_IsFinalizing()) {
99+
// At this phase of runtime shutdown, only the finalization thread
100+
// can have attached thread state; others hang if they try
101+
// attaching. And since operations on this lock requires attached
102+
// thread state (_PY_LOCK_PYTHONLOCK), the finalization thread is
103+
// running this code, and no other thread can unlock.
104+
// Raise rather than hang. (_PY_LOCK_PYTHONLOCK allows raising
105+
// exceptons.)
106+
PyErr_SetString(PyExc_PythonFinalizationError,
107+
"cannot acquire lock at interpreter finalization");
108+
returnPY_LOCK_FAILURE;
109+
}
98110

99111
uint8_tnewv=v;
100112
if (!(v&_Py_HAS_PARKED)) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp