@@ -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+ def test_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 def test_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,
0 commit comments