summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_threading.py
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2019-06-12 23:30:17 (GMT)
committerGitHub <noreply@github.com>2019-06-12 23:30:17 (GMT)
commit468e5fec8a2f534f1685d59da3ca4fad425c38dd (patch)
treeab031e145d86984c0a6236053e8768207e3b2fb2 /Lib/test/test_threading.py
parentb4c7defe58695a6670a8fdeaef67a638bbb47e42 (diff)
downloadcpython-468e5fec8a2f534f1685d59da3ca4fad425c38dd.zip
cpython-468e5fec8a2f534f1685d59da3ca4fad425c38dd.tar.gz
cpython-468e5fec8a2f534f1685d59da3ca4fad425c38dd.tar.bz2
bpo-36402: Fix threading._shutdown() race condition (GH-13948)
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test.
Diffstat (limited to 'Lib/test/test_threading.py')
-rw-r--r--Lib/test/test_threading.py55
1 files changed, 52 insertions, 3 deletions
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 6ac4ea9..ad90010 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -583,6 +583,41 @@ class ThreadTests(BaseTestCase):
self.assertEqual(data.splitlines(),
["GC: True True True"] * 2)
+ def test_finalization_shutdown(self):
+ # bpo-36402: Py_Finalize() calls threading._shutdown() which must wait
+ # until Python thread states of all non-daemon threads get deleted.
+ #
+ # Test similar to SubinterpThreadingTests.test_threads_join_2(), but
+ # test the finalization of the main interpreter.
+ code = """if 1:
+ import os
+ import threading
+ import time
+ import random
+
+ def random_sleep():
+ seconds = random.random() * 0.010
+ time.sleep(seconds)
+
+ class Sleeper:
+ def __del__(self):
+ random_sleep()
+
+ tls = threading.local()
+
+ def f():
+ # Sleep a bit so that the thread is still running when
+ # Py_Finalize() is called.
+ random_sleep()
+ tls.x = Sleeper()
+ random_sleep()
+
+ threading.Thread(target=f).start()
+ random_sleep()
+ """
+ rc, out, err = assert_python_ok("-c", code)
+ self.assertEqual(err, b"")
+
def test_tstate_lock(self):
# Test an implementation detail of Thread objects.
started = _thread.allocate_lock()
@@ -878,15 +913,22 @@ class SubinterpThreadingTests(BaseTestCase):
self.addCleanup(os.close, w)
code = r"""if 1:
import os
+ import random
import threading
import time
+ def random_sleep():
+ seconds = random.random() * 0.010
+ time.sleep(seconds)
+
def f():
# Sleep a bit so that the thread is still running when
# Py_EndInterpreter is called.
- time.sleep(0.05)
+ random_sleep()
os.write(%d, b"x")
+
threading.Thread(target=f).start()
+ random_sleep()
""" % (w,)
ret = test.support.run_in_subinterp(code)
self.assertEqual(ret, 0)
@@ -903,22 +945,29 @@ class SubinterpThreadingTests(BaseTestCase):
self.addCleanup(os.close, w)
code = r"""if 1:
import os
+ import random
import threading
import time
+ def random_sleep():
+ seconds = random.random() * 0.010
+ time.sleep(seconds)
+
class Sleeper:
def __del__(self):
- time.sleep(0.05)
+ random_sleep()
tls = threading.local()
def f():
# Sleep a bit so that the thread is still running when
# Py_EndInterpreter is called.
- time.sleep(0.05)
+ random_sleep()
tls.x = Sleeper()
os.write(%d, b"x")
+
threading.Thread(target=f).start()
+ random_sleep()
""" % (w,)
ret = test.support.run_in_subinterp(code)
self.assertEqual(ret, 0)