changeset: 87070:c2a13acd5e2b user: Victor Stinner date: Tue Nov 12 16:37:55 2013 +0100 files: Lib/test/test_threading.py Misc/NEWS Python/pythonrun.c description: 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. diff -r cb05beabb656 -r c2a13acd5e2b Lib/test/test_threading.py --- a/Lib/test/test_threading.py Tue Nov 12 10:26:15 2013 -0500 +++ b/Lib/test/test_threading.py Tue Nov 12 16:37:55 2013 +0100 @@ -617,6 +617,52 @@ t.join() self.assertRaises(ValueError, bs.release) + def test_locals_at_exit(self): + # Issue #19466: thread locals must not be deleted before destructors + # are called + rc, out, err = assert_python_ok("-c", """if 1: + import threading + + class Atexit: + def __del__(self): + print("thread_dict.atexit = %r" % thread_dict.atexit) + + thread_dict = threading.local() + thread_dict.atexit = "atexit" + + atexit = Atexit() + """) + self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'atexit'") + + def test_warnings_at_exit(self): + # Issue #19466: try to call most destructors at Python shutdown before + # destroying Python thread states + filename = __file__ + rc, out, err = assert_python_ok("-Wd", "-c", """if 1: + import time + import threading + + def open_sleep(): + # a warning will be emitted when the open file will be + # destroyed (without being explicitly closed) while the daemon + # thread is destroyed + fileobj = open(%a, 'rb') + start_event.set() + time.sleep(60.0) + + start_event = threading.Event() + + thread = threading.Thread(target=open_sleep) + thread.daemon = True + thread.start() + + # wait until the thread started + start_event.wait() + """ % filename) + self.assertRegex(err.rstrip(), + b"^sys:1: ResourceWarning: unclosed file ") + + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): @@ -701,6 +747,10 @@ import sys import time import threading + import warnings + + # ignore "unclosed file ..." warnings + warnings.filterwarnings('ignore', '', ResourceWarning) thread_has_run = set() diff -r cb05beabb656 -r c2a13acd5e2b Misc/NEWS --- a/Misc/NEWS Tue Nov 12 10:26:15 2013 -0500 +++ b/Misc/NEWS Tue Nov 12 16:37:55 2013 +0100 @@ -10,6 +10,10 @@ Core and Builtins ----------------- +- Issue #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. + - Issue #19514: Deduplicate some _Py_IDENTIFIER declarations. Patch by Andrei Dorian Duma. diff -r cb05beabb656 -r c2a13acd5e2b Python/pythonrun.c --- a/Python/pythonrun.c Tue Nov 12 10:26:15 2013 -0500 +++ b/Python/pythonrun.c Tue Nov 12 16:37:55 2013 +0100 @@ -576,11 +576,13 @@ _Py_Finalizing = tstate; initialized = 0; - /* Flush stdout+stderr */ - flush_std_files(); - - /* Disable signal handling */ - PyOS_FiniInterrupts(); + /* Destroy the state of all threads except of the current thread: in + practice, only daemon threads should still be alive. Clear frames of + other threads to call objects destructor. Destructors will be called in + the current Python thread. Since _Py_Finalizing has been set, no other + Python threads can lock the GIL at this point (if they try, they will + exit immediatly). */ + _PyThreadState_DeleteExcept(tstate); /* Collect garbage. This may call finalizers; it's nice to call these * before all modules are destroyed. @@ -595,6 +597,7 @@ * XXX I haven't seen a real-life report of either of these. */ PyGC_Collect(); + #ifdef COUNT_ALLOCS /* With COUNT_ALLOCS, it helps to run GC multiple times: each collection might release some types from the type @@ -602,6 +605,13 @@ while (PyGC_Collect() > 0) /* nothing */; #endif + + /* Flush stdout+stderr */ + flush_std_files(); + + /* Disable signal handling */ + PyOS_FiniInterrupts(); + /* Destroy all modules */ PyImport_Cleanup();