Skip to content

Commit 45956b9

Browse files
committed
Close #19466: Clear the frames of daemon threads earlier during the Python
shutdown to call objects destructors. So "unclosed file" resource warnings are now corretly emitted for daemon threads.
1 parent c6a140f commit 45956b9

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

‎Lib/test/test_threading.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,52 @@ def test_BoundedSemaphore_limit(self):
617617
t.join()
618618
self.assertRaises(ValueError, bs.release)
619619

620+
def test_locals_at_exit(self):
621+
# Issue #19466: thread locals must not be deleted before destructors
622+
# are called
623+
rc, out, err = assert_python_ok("-c", """if 1:
624+
import threading
625+
626+
class Atexit:
627+
def __del__(self):
628+
print("thread_dict.atexit = %r" % thread_dict.atexit)
629+
630+
thread_dict = threading.local()
631+
thread_dict.atexit = "atexit"
632+
633+
atexit = Atexit()
634+
""")
635+
self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'atexit'")
636+
637+
def test_warnings_at_exit(self):
638+
# Issue #19466: try to call most destructors at Python shutdown before
639+
# destroying Python thread states
640+
filename = __file__
641+
rc, out, err = assert_python_ok("-Wd", "-c", """if 1:
642+
import time
643+
import threading
644+
645+
def open_sleep():
646+
# a warning will be emitted when the open file will be
647+
# destroyed (without being explicitly closed) while the daemon
648+
# thread is destroyed
649+
fileobj = open(%a, 'rb')
650+
start_event.set()
651+
time.sleep(60.0)
652+
653+
start_event = threading.Event()
654+
655+
thread = threading.Thread(target=open_sleep)
656+
thread.daemon = True
657+
thread.start()
658+
659+
# wait until the thread started
660+
start_event.wait()
661+
""" % filename)
662+
self.assertRegex(err.rstrip(),
663+
b"^sys:1: ResourceWarning: unclosed file ")
664+
665+
620666
class ThreadJoinOnShutdown(BaseTestCase):
621667

622668
def _run_and_join(self, script):
@@ -701,6 +747,10 @@ def test_4_daemon_threads(self):
701747
import sys
702748
import time
703749
import threading
750+
import warnings
751+
752+
# ignore "unclosed file ..." warnings
753+
warnings.filterwarnings('ignore', '', ResourceWarning)
704754
705755
thread_has_run = set()
706756

‎Misc/NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Projected release date: 2013-11-24
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #19466: Clear the frames of daemon threads earlier during the
14+
Python shutdown to call objects destructors. So "unclosed file" resource
15+
warnings are now corretly emitted for daemon threads.
16+
1317
- Issue #19514: Deduplicate some _Py_IDENTIFIER declarations.
1418
Patch by Andrei Dorian Duma.
1519

‎Python/pythonrun.c‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -576,11 +576,13 @@ Py_Finalize(void)
576576
_Py_Finalizing = tstate;
577577
initialized = 0;
578578

579-
/* Flush stdout+stderr */
580-
flush_std_files();
581-
582-
/* Disable signal handling */
583-
PyOS_FiniInterrupts();
579+
/* Destroy the state of all threads except of the current thread: in
580+
practice, only daemon threads should still be alive. Clear frames of
581+
other threads to call objects destructor. Destructors will be called in
582+
the current Python thread. Since _Py_Finalizing has been set, no other
583+
Python threads can lock the GIL at this point (if they try, they will
584+
exit immediatly). */
585+
_PyThreadState_DeleteExcept(tstate);
584586

585587
/* Collect garbage. This may call finalizers; it's nice to call these
586588
* before all modules are destroyed.
@@ -595,13 +597,21 @@ Py_Finalize(void)
595597
* XXX I haven't seen a real-life report of either of these.
596598
*/
597599
PyGC_Collect();
600+
598601
#ifdef COUNT_ALLOCS
599602
/* With COUNT_ALLOCS, it helps to run GC multiple times:
600603
each collection might release some types from the type
601604
list, so they become garbage. */
602605
while (PyGC_Collect() > 0)
603606
/* nothing */;
604607
#endif
608+
609+
/* Flush stdout+stderr */
610+
flush_std_files();
611+
612+
/* Disable signal handling */
613+
PyOS_FiniInterrupts();
614+
605615
/* Destroy all modules */
606616
PyImport_Cleanup();
607617

0 commit comments

Comments
 (0)