Antoine Pitrou | 4c8ce84 | 2013-09-01 19:51:49 +0200 | [diff] [blame] | 1 | """ |
| 2 | Tests for the threading module. |
| 3 | """ |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 4 | |
Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 5 | import test.support |
Hai Shi | e80697d | 2020-05-28 06:10:27 +0800 | [diff] [blame] | 6 | from test.support import threading_helper |
Irit Katriel | 373741a | 2021-05-18 14:53:57 +0100 | [diff] [blame] | 7 | from test.support import verbose, cpython_only, os_helper |
Hai Shi | a7f5d93 | 2020-08-04 00:41:24 +0800 | [diff] [blame] | 8 | from test.support.import_helper import import_module |
Berker Peksag | ce64391 | 2015-05-06 06:33:17 +0300 | [diff] [blame] | 9 | from test.support.script_helper import assert_python_ok, assert_python_failure |
Antoine Pitrou | 8e6e0fd | 2012-04-19 23:55:01 +0200 | [diff] [blame] | 10 | |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 11 | import random |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 12 | import sys |
Antoine Pitrou | a6a4dc8 | 2017-09-07 18:56:24 +0200 | [diff] [blame] | 13 | import _thread |
| 14 | import threading |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 15 | import time |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 16 | import unittest |
Christian Heimes | d3eb5a15 | 2008-02-24 00:38:49 +0000 | [diff] [blame] | 17 | import weakref |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 18 | import os |
Gregory P. Smith | 4b129d2 | 2011-01-04 00:51:50 +0000 | [diff] [blame] | 19 | import subprocess |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 20 | import signal |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 21 | import textwrap |
Irit Katriel | 373741a | 2021-05-18 14:53:57 +0100 | [diff] [blame] | 22 | import traceback |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 23 | |
Victor Stinner | 98c16c9 | 2020-09-23 23:21:19 +0200 | [diff] [blame] | 24 | from unittest import mock |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 25 | from test import lock_tests |
Martin Panter | 19e69c5 | 2015-11-14 12:46:42 +0000 | [diff] [blame] | 26 | from test import support |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 27 | |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 28 | |
| 29 | # Between fork() and exec(), only async-safe functions are allowed (issues |
| 30 | # #12316 and #11870), and fork() from a worker thread is known to trigger |
| 31 | # problems with some operating systems (issue #3863): skip problematic tests |
| 32 | # on platforms known to behave badly. |
Victor Stinner | 13ff245 | 2018-01-22 18:32:50 +0100 | [diff] [blame] | 33 | platforms_to_skip = ('netbsd5', 'hp-ux11') |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 34 | |
Miss Islington (bot) | a11158e | 2021-08-06 04:32:37 -0700 | [diff] [blame^] | 35 | # Is Python built with Py_DEBUG macro defined? |
| 36 | Py_DEBUG = hasattr(sys, 'gettotalrefcount') |
| 37 | |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 38 | |
Victor Stinner | b136b1a | 2021-04-16 14:33:10 +0200 | [diff] [blame] | 39 | def restore_default_excepthook(testcase): |
| 40 | testcase.addCleanup(setattr, threading, 'excepthook', threading.excepthook) |
| 41 | threading.excepthook = threading.__excepthook__ |
| 42 | |
| 43 | |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 44 | # A trivial mutable counter. |
| 45 | class Counter(object): |
| 46 | def __init__(self): |
| 47 | self.value = 0 |
| 48 | def inc(self): |
| 49 | self.value += 1 |
| 50 | def dec(self): |
| 51 | self.value -= 1 |
| 52 | def get(self): |
| 53 | return self.value |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 54 | |
| 55 | class TestThread(threading.Thread): |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 56 | def __init__(self, name, testcase, sema, mutex, nrunning): |
| 57 | threading.Thread.__init__(self, name=name) |
| 58 | self.testcase = testcase |
| 59 | self.sema = sema |
| 60 | self.mutex = mutex |
| 61 | self.nrunning = nrunning |
| 62 | |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 63 | def run(self): |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 64 | delay = random.random() / 10000.0 |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 65 | if verbose: |
Jeffrey Yasskin | ca67412 | 2008-03-29 05:06:52 +0000 | [diff] [blame] | 66 | print('task %s will run for %.1f usec' % |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 67 | (self.name, delay * 1e6)) |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 68 | |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 69 | with self.sema: |
| 70 | with self.mutex: |
| 71 | self.nrunning.inc() |
| 72 | if verbose: |
| 73 | print(self.nrunning.get(), 'tasks are running') |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 74 | self.testcase.assertLessEqual(self.nrunning.get(), 3) |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 75 | |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 76 | time.sleep(delay) |
| 77 | if verbose: |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 78 | print('task', self.name, 'done') |
Benjamin Peterson | 672b803 | 2008-06-11 19:14:14 +0000 | [diff] [blame] | 79 | |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 80 | with self.mutex: |
| 81 | self.nrunning.dec() |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 82 | self.testcase.assertGreaterEqual(self.nrunning.get(), 0) |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 83 | if verbose: |
| 84 | print('%s is finished. %d tasks are running' % |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 85 | (self.name, self.nrunning.get())) |
Benjamin Peterson | 672b803 | 2008-06-11 19:14:14 +0000 | [diff] [blame] | 86 | |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 87 | |
Antoine Pitrou | b0e9bd4 | 2009-10-27 20:05:26 +0000 | [diff] [blame] | 88 | class BaseTestCase(unittest.TestCase): |
| 89 | def setUp(self): |
Hai Shi | e80697d | 2020-05-28 06:10:27 +0800 | [diff] [blame] | 90 | self._threads = threading_helper.threading_setup() |
Antoine Pitrou | b0e9bd4 | 2009-10-27 20:05:26 +0000 | [diff] [blame] | 91 | |
| 92 | def tearDown(self): |
Hai Shi | e80697d | 2020-05-28 06:10:27 +0800 | [diff] [blame] | 93 | threading_helper.threading_cleanup(*self._threads) |
Antoine Pitrou | b0e9bd4 | 2009-10-27 20:05:26 +0000 | [diff] [blame] | 94 | test.support.reap_children() |
| 95 | |
| 96 | |
| 97 | class ThreadTests(BaseTestCase): |
Skip Montanaro | 4533f60 | 2001-08-20 20:28:48 +0000 | [diff] [blame] | 98 | |
Victor Stinner | 98c16c9 | 2020-09-23 23:21:19 +0200 | [diff] [blame] | 99 | @cpython_only |
| 100 | def test_name(self): |
| 101 | def func(): pass |
| 102 | |
| 103 | thread = threading.Thread(name="myname1") |
| 104 | self.assertEqual(thread.name, "myname1") |
| 105 | |
| 106 | # Convert int name to str |
| 107 | thread = threading.Thread(name=123) |
| 108 | self.assertEqual(thread.name, "123") |
| 109 | |
| 110 | # target name is ignored if name is specified |
| 111 | thread = threading.Thread(target=func, name="myname2") |
| 112 | self.assertEqual(thread.name, "myname2") |
| 113 | |
| 114 | with mock.patch.object(threading, '_counter', return_value=2): |
| 115 | thread = threading.Thread(name="") |
| 116 | self.assertEqual(thread.name, "Thread-2") |
| 117 | |
| 118 | with mock.patch.object(threading, '_counter', return_value=3): |
| 119 | thread = threading.Thread() |
| 120 | self.assertEqual(thread.name, "Thread-3") |
| 121 | |
| 122 | with mock.patch.object(threading, '_counter', return_value=5): |
| 123 | thread = threading.Thread(target=func) |
| 124 | self.assertEqual(thread.name, "Thread-5 (func)") |
| 125 | |
Erlend Egeberg Aasland | 9746cda | 2021-04-30 16:04:57 +0200 | [diff] [blame] | 126 | @cpython_only |
| 127 | def test_disallow_instantiation(self): |
| 128 | # Ensure that the type disallows instantiation (bpo-43916) |
| 129 | lock = threading.Lock() |
Erlend Egeberg Aasland | 0a3452e | 2021-06-24 01:46:25 +0200 | [diff] [blame] | 130 | test.support.check_disallow_instantiation(self, type(lock)) |
Erlend Egeberg Aasland | 9746cda | 2021-04-30 16:04:57 +0200 | [diff] [blame] | 131 | |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 132 | # Create a bunch of threads, let each do some work, wait until all are |
| 133 | # done. |
| 134 | def test_various_ops(self): |
| 135 | # This takes about n/3 seconds to run (about n/3 clumps of tasks, |
| 136 | # times about 1 second per clump). |
| 137 | NUMTASKS = 10 |
| 138 | |
| 139 | # no more than 3 of the 10 can run at once |
| 140 | sema = threading.BoundedSemaphore(value=3) |
| 141 | mutex = threading.RLock() |
| 142 | numrunning = Counter() |
| 143 | |
| 144 | threads = [] |
| 145 | |
| 146 | for i in range(NUMTASKS): |
| 147 | t = TestThread("<thread %d>"%i, self, sema, mutex, numrunning) |
| 148 | threads.append(t) |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 149 | self.assertIsNone(t.ident) |
| 150 | self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$') |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 151 | t.start() |
| 152 | |
Jake Tesler | b121f63 | 2019-05-22 08:43:17 -0700 | [diff] [blame] | 153 | if hasattr(threading, 'get_native_id'): |
| 154 | native_ids = set(t.native_id for t in threads) | {threading.get_native_id()} |
| 155 | self.assertNotIn(None, native_ids) |
| 156 | self.assertEqual(len(native_ids), NUMTASKS + 1) |
| 157 | |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 158 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 159 | print('waiting for all tasks to complete') |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 160 | for t in threads: |
Antoine Pitrou | 5da7e79 | 2013-09-08 13:19:06 +0200 | [diff] [blame] | 161 | t.join() |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 162 | self.assertFalse(t.is_alive()) |
Benjamin Peterson | c9c0f20 | 2009-06-30 23:06:06 +0000 | [diff] [blame] | 163 | self.assertNotEqual(t.ident, 0) |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 164 | self.assertIsNotNone(t.ident) |
| 165 | self.assertRegex(repr(t), r'^<TestThread\(.*, stopped -?\d+\)>$') |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 166 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 167 | print('all tasks done') |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 168 | self.assertEqual(numrunning.get(), 0) |
| 169 | |
Benjamin Peterson | d23f822 | 2009-04-05 19:13:16 +0000 | [diff] [blame] | 170 | def test_ident_of_no_threading_threads(self): |
| 171 | # The ident still must work for the main thread and dummy threads. |
Jelle Zijlstra | 9825bdf | 2021-04-12 01:42:53 -0700 | [diff] [blame] | 172 | self.assertIsNotNone(threading.current_thread().ident) |
Benjamin Peterson | d23f822 | 2009-04-05 19:13:16 +0000 | [diff] [blame] | 173 | def f(): |
Jelle Zijlstra | 9825bdf | 2021-04-12 01:42:53 -0700 | [diff] [blame] | 174 | ident.append(threading.current_thread().ident) |
Benjamin Peterson | d23f822 | 2009-04-05 19:13:16 +0000 | [diff] [blame] | 175 | done.set() |
| 176 | done = threading.Event() |
| 177 | ident = [] |
Hai Shi | e80697d | 2020-05-28 06:10:27 +0800 | [diff] [blame] | 178 | with threading_helper.wait_threads_exit(): |
Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 179 | tid = _thread.start_new_thread(f, ()) |
| 180 | done.wait() |
| 181 | self.assertEqual(ident[0], tid) |
Antoine Pitrou | ca13a0d | 2009-11-08 00:30:04 +0000 | [diff] [blame] | 182 | # Kill the "immortal" _DummyThread |
| 183 | del threading._active[ident[0]] |
Benjamin Peterson | d23f822 | 2009-04-05 19:13:16 +0000 | [diff] [blame] | 184 | |
Victor Stinner | 8c663fd | 2017-11-08 14:44:44 -0800 | [diff] [blame] | 185 | # run with a small(ish) thread stack size (256 KiB) |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 186 | def test_various_ops_small_stack(self): |
| 187 | if verbose: |
Victor Stinner | 8c663fd | 2017-11-08 14:44:44 -0800 | [diff] [blame] | 188 | print('with 256 KiB thread stack size...') |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 189 | try: |
| 190 | threading.stack_size(262144) |
Georg Brandl | 2067bfd | 2008-05-25 13:05:15 +0000 | [diff] [blame] | 191 | except _thread.error: |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 192 | raise unittest.SkipTest( |
| 193 | 'platform does not support changing thread stack size') |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 194 | self.test_various_ops() |
| 195 | threading.stack_size(0) |
| 196 | |
Victor Stinner | 8c663fd | 2017-11-08 14:44:44 -0800 | [diff] [blame] | 197 | # run with a large thread stack size (1 MiB) |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 198 | def test_various_ops_large_stack(self): |
| 199 | if verbose: |
Victor Stinner | 8c663fd | 2017-11-08 14:44:44 -0800 | [diff] [blame] | 200 | print('with 1 MiB thread stack size...') |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 201 | try: |
| 202 | threading.stack_size(0x100000) |
Georg Brandl | 2067bfd | 2008-05-25 13:05:15 +0000 | [diff] [blame] | 203 | except _thread.error: |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 204 | raise unittest.SkipTest( |
| 205 | 'platform does not support changing thread stack size') |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 206 | self.test_various_ops() |
| 207 | threading.stack_size(0) |
| 208 | |
Tim Peters | 711906e | 2005-01-08 07:30:42 +0000 | [diff] [blame] | 209 | def test_foreign_thread(self): |
| 210 | # Check that a "foreign" thread can use the threading module. |
| 211 | def f(mutex): |
Antoine Pitrou | b087268 | 2009-11-09 16:08:16 +0000 | [diff] [blame] | 212 | # Calling current_thread() forces an entry for the foreign |
Tim Peters | 711906e | 2005-01-08 07:30:42 +0000 | [diff] [blame] | 213 | # thread to get made in the threading._active map. |
Antoine Pitrou | b087268 | 2009-11-09 16:08:16 +0000 | [diff] [blame] | 214 | threading.current_thread() |
Tim Peters | 711906e | 2005-01-08 07:30:42 +0000 | [diff] [blame] | 215 | mutex.release() |
| 216 | |
| 217 | mutex = threading.Lock() |
| 218 | mutex.acquire() |
Hai Shi | e80697d | 2020-05-28 06:10:27 +0800 | [diff] [blame] | 219 | with threading_helper.wait_threads_exit(): |
Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 220 | tid = _thread.start_new_thread(f, (mutex,)) |
| 221 | # Wait for the thread to finish. |
| 222 | mutex.acquire() |
Benjamin Peterson | 577473f | 2010-01-19 00:09:57 +0000 | [diff] [blame] | 223 | self.assertIn(tid, threading._active) |
Ezio Melotti | e961593 | 2010-01-24 19:26:24 +0000 | [diff] [blame] | 224 | self.assertIsInstance(threading._active[tid], threading._DummyThread) |
Xiang Zhang | f3a9fab | 2017-02-27 11:01:30 +0800 | [diff] [blame] | 225 | #Issue 29376 |
| 226 | self.assertTrue(threading._active[tid].is_alive()) |
| 227 | self.assertRegex(repr(threading._active[tid]), '_DummyThread') |
Tim Peters | 711906e | 2005-01-08 07:30:42 +0000 | [diff] [blame] | 228 | del threading._active[tid] |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 229 | |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 230 | # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently) |
| 231 | # exposed at the Python level. This test relies on ctypes to get at it. |
| 232 | def test_PyThreadState_SetAsyncExc(self): |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 233 | ctypes = import_module("ctypes") |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 234 | |
| 235 | set_async_exc = ctypes.pythonapi.PyThreadState_SetAsyncExc |
Serhiy Storchaka | aefa7eb | 2017-03-23 15:48:39 +0200 | [diff] [blame] | 236 | set_async_exc.argtypes = (ctypes.c_ulong, ctypes.py_object) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 237 | |
| 238 | class AsyncExc(Exception): |
| 239 | pass |
| 240 | |
| 241 | exception = ctypes.py_object(AsyncExc) |
| 242 | |
Antoine Pitrou | be4d809 | 2009-10-18 18:27:17 +0000 | [diff] [blame] | 243 | # First check it works when setting the exception from the same thread. |
Victor Stinner | 2a12974 | 2011-05-30 23:02:52 +0200 | [diff] [blame] | 244 | tid = threading.get_ident() |
Serhiy Storchaka | aefa7eb | 2017-03-23 15:48:39 +0200 | [diff] [blame] | 245 | self.assertIsInstance(tid, int) |
| 246 | self.assertGreater(tid, 0) |
Antoine Pitrou | be4d809 | 2009-10-18 18:27:17 +0000 | [diff] [blame] | 247 | |
| 248 | try: |
Serhiy Storchaka | aefa7eb | 2017-03-23 15:48:39 +0200 | [diff] [blame] | 249 | result = set_async_exc(tid, exception) |
Antoine Pitrou | be4d809 | 2009-10-18 18:27:17 +0000 | [diff] [blame] | 250 | # The exception is async, so we might have to keep the VM busy until |
| 251 | # it notices. |
| 252 | while True: |
| 253 | pass |
| 254 | except AsyncExc: |
| 255 | pass |
| 256 | else: |
Benjamin Peterson | a0dfa82 | 2009-11-13 02:25:08 +0000 | [diff] [blame] | 257 | # This code is unreachable but it reflects the intent. If we wanted |
| 258 | # to be smarter the above loop wouldn't be infinite. |
Antoine Pitrou | be4d809 | 2009-10-18 18:27:17 +0000 | [diff] [blame] | 259 | self.fail("AsyncExc not raised") |
| 260 | try: |
| 261 | self.assertEqual(result, 1) # one thread state modified |
| 262 | except UnboundLocalError: |
Benjamin Peterson | a0dfa82 | 2009-11-13 02:25:08 +0000 | [diff] [blame] | 263 | # The exception was raised too quickly for us to get the result. |
Antoine Pitrou | be4d809 | 2009-10-18 18:27:17 +0000 | [diff] [blame] | 264 | pass |
| 265 | |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 266 | # `worker_started` is set by the thread when it's inside a try/except |
| 267 | # block waiting to catch the asynchronously set AsyncExc exception. |
| 268 | # `worker_saw_exception` is set by the thread upon catching that |
| 269 | # exception. |
| 270 | worker_started = threading.Event() |
| 271 | worker_saw_exception = threading.Event() |
| 272 | |
| 273 | class Worker(threading.Thread): |
| 274 | def run(self): |
Victor Stinner | 2a12974 | 2011-05-30 23:02:52 +0200 | [diff] [blame] | 275 | self.id = threading.get_ident() |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 276 | self.finished = False |
| 277 | |
| 278 | try: |
| 279 | while True: |
| 280 | worker_started.set() |
| 281 | time.sleep(0.1) |
| 282 | except AsyncExc: |
| 283 | self.finished = True |
| 284 | worker_saw_exception.set() |
| 285 | |
| 286 | t = Worker() |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 287 | t.daemon = True # so if this fails, we don't hang Python at shutdown |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 288 | t.start() |
| 289 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 290 | print(" started worker thread") |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 291 | |
| 292 | # Try a thread id that doesn't make sense. |
| 293 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 294 | print(" trying nonsensical thread id") |
Serhiy Storchaka | aefa7eb | 2017-03-23 15:48:39 +0200 | [diff] [blame] | 295 | result = set_async_exc(-1, exception) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 296 | self.assertEqual(result, 0) # no thread states modified |
| 297 | |
| 298 | # Now raise an exception in the worker thread. |
| 299 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 300 | print(" waiting for worker thread to get started") |
Benjamin Peterson | d23f822 | 2009-04-05 19:13:16 +0000 | [diff] [blame] | 301 | ret = worker_started.wait() |
| 302 | self.assertTrue(ret) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 303 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 304 | print(" verifying worker hasn't exited") |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 305 | self.assertFalse(t.finished) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 306 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 307 | print(" attempting to raise asynch exception in worker") |
Serhiy Storchaka | aefa7eb | 2017-03-23 15:48:39 +0200 | [diff] [blame] | 308 | result = set_async_exc(t.id, exception) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 309 | self.assertEqual(result, 1) # one thread state modified |
| 310 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 311 | print(" waiting for worker to say it caught the exception") |
Victor Stinner | 0d63bac | 2019-12-11 11:30:03 +0100 | [diff] [blame] | 312 | worker_saw_exception.wait(timeout=support.SHORT_TIMEOUT) |
Benjamin Peterson | c9c0f20 | 2009-06-30 23:06:06 +0000 | [diff] [blame] | 313 | self.assertTrue(t.finished) |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 314 | if verbose: |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 315 | print(" all OK -- joining worker") |
Thomas Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 316 | if t.finished: |
| 317 | t.join() |
| 318 | # else the thread is still running, and we have no way to kill it |
| 319 | |
Gregory P. Smith | 3fdd964 | 2010-02-28 18:57:46 +0000 | [diff] [blame] | 320 | def test_limbo_cleanup(self): |
| 321 | # Issue 7481: Failure to start thread should cleanup the limbo map. |
| 322 | def fail_new_thread(*args): |
| 323 | raise threading.ThreadError() |
| 324 | _start_new_thread = threading._start_new_thread |
| 325 | threading._start_new_thread = fail_new_thread |
| 326 | try: |
| 327 | t = threading.Thread(target=lambda: None) |
Gregory P. Smith | f50f168 | 2010-03-01 03:13:36 +0000 | [diff] [blame] | 328 | self.assertRaises(threading.ThreadError, t.start) |
| 329 | self.assertFalse( |
| 330 | t in threading._limbo, |
| 331 | "Failed to cleanup _limbo map on failure of Thread.start().") |
Gregory P. Smith | 3fdd964 | 2010-02-28 18:57:46 +0000 | [diff] [blame] | 332 | finally: |
| 333 | threading._start_new_thread = _start_new_thread |
| 334 | |
Min ho Kim | c4cacc8 | 2019-07-31 08:16:13 +1000 | [diff] [blame] | 335 | def test_finalize_running_thread(self): |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 336 | # Issue 1402: the PyGILState_Ensure / _Release functions may be called |
| 337 | # very late on python exit: on deallocation of a running thread for |
| 338 | # example. |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 339 | import_module("ctypes") |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 340 | |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 341 | rc, out, err = assert_python_failure("-c", """if 1: |
Georg Brandl | 2067bfd | 2008-05-25 13:05:15 +0000 | [diff] [blame] | 342 | import ctypes, sys, time, _thread |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 343 | |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 344 | # This lock is used as a simple event variable. |
Georg Brandl | 2067bfd | 2008-05-25 13:05:15 +0000 | [diff] [blame] | 345 | ready = _thread.allocate_lock() |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 346 | ready.acquire() |
| 347 | |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 348 | # Module globals are cleared before __del__ is run |
| 349 | # So we save the functions in class dict |
| 350 | class C: |
| 351 | ensure = ctypes.pythonapi.PyGILState_Ensure |
| 352 | release = ctypes.pythonapi.PyGILState_Release |
| 353 | def __del__(self): |
| 354 | state = self.ensure() |
| 355 | self.release(state) |
| 356 | |
| 357 | def waitingThread(): |
| 358 | x = C() |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 359 | ready.release() |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 360 | time.sleep(100) |
| 361 | |
Georg Brandl | 2067bfd | 2008-05-25 13:05:15 +0000 | [diff] [blame] | 362 | _thread.start_new_thread(waitingThread, ()) |
Christian Heimes | 4fbc72b | 2008-03-22 00:47:35 +0000 | [diff] [blame] | 363 | ready.acquire() # Be sure the other thread is waiting. |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 364 | sys.exit(42) |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 365 | """) |
Christian Heimes | 7d2ff88 | 2007-11-30 14:35:04 +0000 | [diff] [blame] | 366 | self.assertEqual(rc, 42) |
| 367 | |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 368 | def test_finalize_with_trace(self): |
| 369 | # Issue1733757 |
| 370 | # Avoid a deadlock when sys.settrace steps into threading._shutdown |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 371 | assert_python_ok("-c", """if 1: |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 372 | import sys, threading |
| 373 | |
| 374 | # A deadlock-killer, to prevent the |
| 375 | # testsuite to hang forever |
| 376 | def killer(): |
| 377 | import os, time |
| 378 | time.sleep(2) |
| 379 | print('program blocked; aborting') |
| 380 | os._exit(2) |
| 381 | t = threading.Thread(target=killer) |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 382 | t.daemon = True |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 383 | t.start() |
| 384 | |
| 385 | # This is the trace function |
| 386 | def func(frame, event, arg): |
Benjamin Peterson | 672b803 | 2008-06-11 19:14:14 +0000 | [diff] [blame] | 387 | threading.current_thread() |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 388 | return func |
| 389 | |
| 390 | sys.settrace(func) |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 391 | """) |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 392 | |
Antoine Pitrou | 011bd62 | 2009-10-20 21:52:47 +0000 | [diff] [blame] | 393 | def test_join_nondaemon_on_shutdown(self): |
| 394 | # Issue 1722344 |
| 395 | # Raising SystemExit skipped threading._shutdown |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 396 | rc, out, err = assert_python_ok("-c", """if 1: |
Antoine Pitrou | 011bd62 | 2009-10-20 21:52:47 +0000 | [diff] [blame] | 397 | import threading |
| 398 | from time import sleep |
| 399 | |
| 400 | def child(): |
| 401 | sleep(1) |
| 402 | # As a non-daemon thread we SHOULD wake up and nothing |
| 403 | # should be torn down yet |
| 404 | print("Woke up, sleep function is:", sleep) |
| 405 | |
| 406 | threading.Thread(target=child).start() |
| 407 | raise SystemExit |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 408 | """) |
| 409 | self.assertEqual(out.strip(), |
Antoine Pitrou | 899d1c6 | 2009-10-23 21:55:36 +0000 | [diff] [blame] | 410 | b"Woke up, sleep function is: <built-in function sleep>") |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 411 | self.assertEqual(err, b"") |
Neal Norwitz | f5c7c2e | 2008-04-05 04:47:45 +0000 | [diff] [blame] | 412 | |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 413 | def test_enumerate_after_join(self): |
| 414 | # Try hard to trigger #1703448: a thread is still returned in |
| 415 | # threading.enumerate() after it has been join()ed. |
| 416 | enum = threading.enumerate |
Antoine Pitrou | c3b0757 | 2009-11-13 22:19:19 +0000 | [diff] [blame] | 417 | old_interval = sys.getswitchinterval() |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 418 | try: |
Jeffrey Yasskin | ca67412 | 2008-03-29 05:06:52 +0000 | [diff] [blame] | 419 | for i in range(1, 100): |
Antoine Pitrou | c3b0757 | 2009-11-13 22:19:19 +0000 | [diff] [blame] | 420 | sys.setswitchinterval(i * 0.0002) |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 421 | t = threading.Thread(target=lambda: None) |
| 422 | t.start() |
| 423 | t.join() |
| 424 | l = enum() |
Ezio Melotti | b58e0bd | 2010-01-23 15:40:09 +0000 | [diff] [blame] | 425 | self.assertNotIn(t, l, |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 426 | "#1703448 triggered after %d trials: %s" % (i, l)) |
| 427 | finally: |
Antoine Pitrou | c3b0757 | 2009-11-13 22:19:19 +0000 | [diff] [blame] | 428 | sys.setswitchinterval(old_interval) |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 429 | |
Christian Heimes | d3eb5a15 | 2008-02-24 00:38:49 +0000 | [diff] [blame] | 430 | def test_no_refcycle_through_target(self): |
| 431 | class RunSelfFunction(object): |
| 432 | def __init__(self, should_raise): |
| 433 | # The links in this refcycle from Thread back to self |
| 434 | # should be cleaned up when the thread completes. |
| 435 | self.should_raise = should_raise |
| 436 | self.thread = threading.Thread(target=self._run, |
| 437 | args=(self,), |
| 438 | kwargs={'yet_another':self}) |
| 439 | self.thread.start() |
| 440 | |
| 441 | def _run(self, other_ref, yet_another): |
| 442 | if self.should_raise: |
| 443 | raise SystemExit |
| 444 | |
Victor Stinner | b136b1a | 2021-04-16 14:33:10 +0200 | [diff] [blame] | 445 | restore_default_excepthook(self) |
| 446 | |
Christian Heimes | d3eb5a15 | 2008-02-24 00:38:49 +0000 | [diff] [blame] | 447 | cyclic_object = RunSelfFunction(should_raise=False) |
| 448 | weak_cyclic_object = weakref.ref(cyclic_object) |
| 449 | cyclic_object.thread.join() |
| 450 | del cyclic_object |
Raymond Hettinger | 7beae8a | 2011-01-06 05:34:17 +0000 | [diff] [blame] | 451 | self.assertIsNone(weak_cyclic_object(), |
Ezio Melotti | b3aedd4 | 2010-11-20 19:04:17 +0000 | [diff] [blame] | 452 | msg=('%d references still around' % |
| 453 | sys.getrefcount(weak_cyclic_object()))) |
Christian Heimes | d3eb5a15 | 2008-02-24 00:38:49 +0000 | [diff] [blame] | 454 | |
| 455 | raising_cyclic_object = RunSelfFunction(should_raise=True) |
| 456 | weak_raising_cyclic_object = weakref.ref(raising_cyclic_object) |
| 457 | raising_cyclic_object.thread.join() |
| 458 | del raising_cyclic_object |
Raymond Hettinger | 7beae8a | 2011-01-06 05:34:17 +0000 | [diff] [blame] | 459 | self.assertIsNone(weak_raising_cyclic_object(), |
Ezio Melotti | b3aedd4 | 2010-11-20 19:04:17 +0000 | [diff] [blame] | 460 | msg=('%d references still around' % |
| 461 | sys.getrefcount(weak_raising_cyclic_object()))) |
Christian Heimes | d3eb5a15 | 2008-02-24 00:38:49 +0000 | [diff] [blame] | 462 | |
Benjamin Peterson | b3085c9 | 2008-09-01 23:09:31 +0000 | [diff] [blame] | 463 | def test_old_threading_api(self): |
| 464 | # Just a quick sanity check to make sure the old method names are |
| 465 | # still present |
Benjamin Peterson | f0923f5 | 2008-08-18 22:10:13 +0000 | [diff] [blame] | 466 | t = threading.Thread() |
Jelle Zijlstra | 9825bdf | 2021-04-12 01:42:53 -0700 | [diff] [blame] | 467 | with self.assertWarnsRegex(DeprecationWarning, |
| 468 | r'get the daemon attribute'): |
| 469 | t.isDaemon() |
| 470 | with self.assertWarnsRegex(DeprecationWarning, |
| 471 | r'set the daemon attribute'): |
| 472 | t.setDaemon(True) |
| 473 | with self.assertWarnsRegex(DeprecationWarning, |
| 474 | r'get the name attribute'): |
| 475 | t.getName() |
| 476 | with self.assertWarnsRegex(DeprecationWarning, |
| 477 | r'set the name attribute'): |
| 478 | t.setName("name") |
| 479 | |
Benjamin Peterson | b3085c9 | 2008-09-01 23:09:31 +0000 | [diff] [blame] | 480 | e = threading.Event() |
Jelle Zijlstra | 9825bdf | 2021-04-12 01:42:53 -0700 | [diff] [blame] | 481 | with self.assertWarnsRegex(DeprecationWarning, 'use is_set()'): |
| 482 | e.isSet() |
| 483 | |
| 484 | cond = threading.Condition() |
| 485 | cond.acquire() |
| 486 | with self.assertWarnsRegex(DeprecationWarning, 'use notify_all()'): |
| 487 | cond.notifyAll() |
| 488 | |
| 489 | with self.assertWarnsRegex(DeprecationWarning, 'use active_count()'): |
| 490 | threading.activeCount() |
| 491 | with self.assertWarnsRegex(DeprecationWarning, 'use current_thread()'): |
| 492 | threading.currentThread() |
Benjamin Peterson | f0923f5 | 2008-08-18 22:10:13 +0000 | [diff] [blame] | 493 | |
Brian Curtin | 81a4a6a | 2010-07-23 16:30:10 +0000 | [diff] [blame] | 494 | def test_repr_daemon(self): |
| 495 | t = threading.Thread() |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 496 | self.assertNotIn('daemon', repr(t)) |
Brian Curtin | 81a4a6a | 2010-07-23 16:30:10 +0000 | [diff] [blame] | 497 | t.daemon = True |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 498 | self.assertIn('daemon', repr(t)) |
Brett Cannon | 3f5f226 | 2010-07-23 15:50:52 +0000 | [diff] [blame] | 499 | |
luzpaz | a5293b4 | 2017-11-05 07:37:50 -0600 | [diff] [blame] | 500 | def test_daemon_param(self): |
Antoine Pitrou | 0bd4deb | 2011-02-25 22:07:43 +0000 | [diff] [blame] | 501 | t = threading.Thread() |
| 502 | self.assertFalse(t.daemon) |
| 503 | t = threading.Thread(daemon=False) |
| 504 | self.assertFalse(t.daemon) |
| 505 | t = threading.Thread(daemon=True) |
| 506 | self.assertTrue(t.daemon) |
| 507 | |
Victor Stinner | 5909a49 | 2020-11-16 15:20:34 +0100 | [diff] [blame] | 508 | @unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()') |
| 509 | def test_fork_at_exit(self): |
| 510 | # bpo-42350: Calling os.fork() after threading._shutdown() must |
| 511 | # not log an error. |
| 512 | code = textwrap.dedent(""" |
| 513 | import atexit |
| 514 | import os |
| 515 | import sys |
| 516 | from test.support import wait_process |
| 517 | |
| 518 | # Import the threading module to register its "at fork" callback |
| 519 | import threading |
| 520 | |
| 521 | def exit_handler(): |
| 522 | pid = os.fork() |
| 523 | if not pid: |
| 524 | print("child process ok", file=sys.stderr, flush=True) |
| 525 | # child process |
Victor Stinner | 5909a49 | 2020-11-16 15:20:34 +0100 | [diff] [blame] | 526 | else: |
| 527 | wait_process(pid, exitcode=0) |
| 528 | |
| 529 | # exit_handler() will be called after threading._shutdown() |
| 530 | atexit.register(exit_handler) |
| 531 | """) |
| 532 | _, out, err = assert_python_ok("-c", code) |
| 533 | self.assertEqual(out, b'') |
| 534 | self.assertEqual(err.rstrip(), b'child process ok') |
| 535 | |
Antoine Pitrou | 8e6e0fd | 2012-04-19 23:55:01 +0200 | [diff] [blame] | 536 | @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') |
| 537 | def test_dummy_thread_after_fork(self): |
| 538 | # Issue #14308: a dummy thread in the active list doesn't mess up |
| 539 | # the after-fork mechanism. |
| 540 | code = """if 1: |
| 541 | import _thread, threading, os, time |
| 542 | |
| 543 | def background_thread(evt): |
| 544 | # Creates and registers the _DummyThread instance |
| 545 | threading.current_thread() |
| 546 | evt.set() |
| 547 | time.sleep(10) |
| 548 | |
| 549 | evt = threading.Event() |
| 550 | _thread.start_new_thread(background_thread, (evt,)) |
| 551 | evt.wait() |
| 552 | assert threading.active_count() == 2, threading.active_count() |
| 553 | if os.fork() == 0: |
| 554 | assert threading.active_count() == 1, threading.active_count() |
| 555 | os._exit(0) |
| 556 | else: |
| 557 | os.wait() |
| 558 | """ |
| 559 | _, out, err = assert_python_ok("-c", code) |
| 560 | self.assertEqual(out, b'') |
| 561 | self.assertEqual(err, b'') |
| 562 | |
Charles-François Natali | 9939cc8 | 2013-08-30 23:32:53 +0200 | [diff] [blame] | 563 | @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") |
| 564 | def test_is_alive_after_fork(self): |
| 565 | # Try hard to trigger #18418: is_alive() could sometimes be True on |
| 566 | # threads that vanished after a fork. |
| 567 | old_interval = sys.getswitchinterval() |
| 568 | self.addCleanup(sys.setswitchinterval, old_interval) |
| 569 | |
| 570 | # Make the bug more likely to manifest. |
Xavier de Gaye | cb9ab0f | 2016-12-08 12:21:00 +0100 | [diff] [blame] | 571 | test.support.setswitchinterval(1e-6) |
Charles-François Natali | 9939cc8 | 2013-08-30 23:32:53 +0200 | [diff] [blame] | 572 | |
| 573 | for i in range(20): |
| 574 | t = threading.Thread(target=lambda: None) |
| 575 | t.start() |
Charles-François Natali | 9939cc8 | 2013-08-30 23:32:53 +0200 | [diff] [blame] | 576 | pid = os.fork() |
| 577 | if pid == 0: |
Victor Stinner | f8d05b3 | 2017-05-17 11:58:50 -0700 | [diff] [blame] | 578 | os._exit(11 if t.is_alive() else 10) |
Charles-François Natali | 9939cc8 | 2013-08-30 23:32:53 +0200 | [diff] [blame] | 579 | else: |
Victor Stinner | f8d05b3 | 2017-05-17 11:58:50 -0700 | [diff] [blame] | 580 | t.join() |
| 581 | |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 582 | support.wait_process(pid, exitcode=10) |
Charles-François Natali | 9939cc8 | 2013-08-30 23:32:53 +0200 | [diff] [blame] | 583 | |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 584 | def test_main_thread(self): |
| 585 | main = threading.main_thread() |
| 586 | self.assertEqual(main.name, 'MainThread') |
| 587 | self.assertEqual(main.ident, threading.current_thread().ident) |
| 588 | self.assertEqual(main.ident, threading.get_ident()) |
| 589 | |
| 590 | def f(): |
| 591 | self.assertNotEqual(threading.main_thread().ident, |
| 592 | threading.current_thread().ident) |
| 593 | th = threading.Thread(target=f) |
| 594 | th.start() |
| 595 | th.join() |
| 596 | |
| 597 | @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()") |
| 598 | @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()") |
| 599 | def test_main_thread_after_fork(self): |
| 600 | code = """if 1: |
| 601 | import os, threading |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 602 | from test import support |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 603 | |
| 604 | pid = os.fork() |
| 605 | if pid == 0: |
| 606 | main = threading.main_thread() |
| 607 | print(main.name) |
| 608 | print(main.ident == threading.current_thread().ident) |
| 609 | print(main.ident == threading.get_ident()) |
| 610 | else: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 611 | support.wait_process(pid, exitcode=0) |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 612 | """ |
| 613 | _, out, err = assert_python_ok("-c", code) |
| 614 | data = out.decode().replace('\r', '') |
| 615 | self.assertEqual(err, b"") |
| 616 | self.assertEqual(data, "MainThread\nTrue\nTrue\n") |
| 617 | |
| 618 | @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") |
| 619 | @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()") |
| 620 | @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()") |
| 621 | def test_main_thread_after_fork_from_nonmain_thread(self): |
| 622 | code = """if 1: |
| 623 | import os, threading, sys |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 624 | from test import support |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 625 | |
Victor Stinner | 98c16c9 | 2020-09-23 23:21:19 +0200 | [diff] [blame] | 626 | def func(): |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 627 | pid = os.fork() |
| 628 | if pid == 0: |
| 629 | main = threading.main_thread() |
| 630 | print(main.name) |
| 631 | print(main.ident == threading.current_thread().ident) |
| 632 | print(main.ident == threading.get_ident()) |
| 633 | # stdout is fully buffered because not a tty, |
| 634 | # we have to flush before exit. |
| 635 | sys.stdout.flush() |
| 636 | else: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 637 | support.wait_process(pid, exitcode=0) |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 638 | |
Victor Stinner | 98c16c9 | 2020-09-23 23:21:19 +0200 | [diff] [blame] | 639 | th = threading.Thread(target=func) |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 640 | th.start() |
| 641 | th.join() |
| 642 | """ |
| 643 | _, out, err = assert_python_ok("-c", code) |
| 644 | data = out.decode().replace('\r', '') |
| 645 | self.assertEqual(err, b"") |
Victor Stinner | 98c16c9 | 2020-09-23 23:21:19 +0200 | [diff] [blame] | 646 | self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n") |
Andrew Svetlov | 58b5c5a | 2013-09-04 07:01:07 +0300 | [diff] [blame] | 647 | |
Antoine Pitrou | 1023dbb | 2017-10-02 16:42:15 +0200 | [diff] [blame] | 648 | def test_main_thread_during_shutdown(self): |
| 649 | # bpo-31516: current_thread() should still point to the main thread |
| 650 | # at shutdown |
| 651 | code = """if 1: |
| 652 | import gc, threading |
| 653 | |
| 654 | main_thread = threading.current_thread() |
| 655 | assert main_thread is threading.main_thread() # sanity check |
| 656 | |
| 657 | class RefCycle: |
| 658 | def __init__(self): |
| 659 | self.cycle = self |
| 660 | |
| 661 | def __del__(self): |
| 662 | print("GC:", |
| 663 | threading.current_thread() is main_thread, |
| 664 | threading.main_thread() is main_thread, |
| 665 | threading.enumerate() == [main_thread]) |
| 666 | |
| 667 | RefCycle() |
| 668 | gc.collect() # sanity check |
| 669 | x = RefCycle() |
| 670 | """ |
| 671 | _, out, err = assert_python_ok("-c", code) |
| 672 | data = out.decode() |
| 673 | self.assertEqual(err, b"") |
| 674 | self.assertEqual(data.splitlines(), |
| 675 | ["GC: True True True"] * 2) |
| 676 | |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 677 | def test_finalization_shutdown(self): |
| 678 | # bpo-36402: Py_Finalize() calls threading._shutdown() which must wait |
| 679 | # until Python thread states of all non-daemon threads get deleted. |
| 680 | # |
| 681 | # Test similar to SubinterpThreadingTests.test_threads_join_2(), but |
| 682 | # test the finalization of the main interpreter. |
| 683 | code = """if 1: |
| 684 | import os |
| 685 | import threading |
| 686 | import time |
| 687 | import random |
| 688 | |
| 689 | def random_sleep(): |
| 690 | seconds = random.random() * 0.010 |
| 691 | time.sleep(seconds) |
| 692 | |
| 693 | class Sleeper: |
| 694 | def __del__(self): |
| 695 | random_sleep() |
| 696 | |
| 697 | tls = threading.local() |
| 698 | |
| 699 | def f(): |
| 700 | # Sleep a bit so that the thread is still running when |
| 701 | # Py_Finalize() is called. |
| 702 | random_sleep() |
| 703 | tls.x = Sleeper() |
| 704 | random_sleep() |
| 705 | |
| 706 | threading.Thread(target=f).start() |
| 707 | random_sleep() |
| 708 | """ |
| 709 | rc, out, err = assert_python_ok("-c", code) |
| 710 | self.assertEqual(err, b"") |
| 711 | |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 712 | def test_tstate_lock(self): |
| 713 | # Test an implementation detail of Thread objects. |
| 714 | started = _thread.allocate_lock() |
| 715 | finish = _thread.allocate_lock() |
| 716 | started.acquire() |
| 717 | finish.acquire() |
| 718 | def f(): |
| 719 | started.release() |
| 720 | finish.acquire() |
| 721 | time.sleep(0.01) |
| 722 | # The tstate lock is None until the thread is started |
| 723 | t = threading.Thread(target=f) |
| 724 | self.assertIs(t._tstate_lock, None) |
| 725 | t.start() |
| 726 | started.acquire() |
| 727 | self.assertTrue(t.is_alive()) |
| 728 | # The tstate lock can't be acquired when the thread is running |
| 729 | # (or suspended). |
| 730 | tstate_lock = t._tstate_lock |
| 731 | self.assertFalse(tstate_lock.acquire(timeout=0), False) |
| 732 | finish.release() |
| 733 | # When the thread ends, the state_lock can be successfully |
| 734 | # acquired. |
Victor Stinner | 0d63bac | 2019-12-11 11:30:03 +0100 | [diff] [blame] | 735 | self.assertTrue(tstate_lock.acquire(timeout=support.SHORT_TIMEOUT), False) |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 736 | # But is_alive() is still True: we hold _tstate_lock now, which |
| 737 | # prevents is_alive() from knowing the thread's end-of-life C code |
| 738 | # is done. |
| 739 | self.assertTrue(t.is_alive()) |
| 740 | # Let is_alive() find out the C code is done. |
| 741 | tstate_lock.release() |
| 742 | self.assertFalse(t.is_alive()) |
| 743 | # And verify the thread disposed of _tstate_lock. |
Serhiy Storchaka | 8c0f0c5 | 2016-03-14 10:28:59 +0200 | [diff] [blame] | 744 | self.assertIsNone(t._tstate_lock) |
Victor Stinner | b8c7be2 | 2017-09-14 13:05:21 -0700 | [diff] [blame] | 745 | t.join() |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 746 | |
Tim Peters | 72460fa | 2013-09-09 18:48:24 -0500 | [diff] [blame] | 747 | def test_repr_stopped(self): |
| 748 | # Verify that "stopped" shows up in repr(Thread) appropriately. |
| 749 | started = _thread.allocate_lock() |
| 750 | finish = _thread.allocate_lock() |
| 751 | started.acquire() |
| 752 | finish.acquire() |
| 753 | def f(): |
| 754 | started.release() |
| 755 | finish.acquire() |
| 756 | t = threading.Thread(target=f) |
| 757 | t.start() |
| 758 | started.acquire() |
| 759 | self.assertIn("started", repr(t)) |
| 760 | finish.release() |
| 761 | # "stopped" should appear in the repr in a reasonable amount of time. |
| 762 | # Implementation detail: as of this writing, that's trivially true |
| 763 | # if .join() is called, and almost trivially true if .is_alive() is |
| 764 | # called. The detail we're testing here is that "stopped" shows up |
| 765 | # "all on its own". |
| 766 | LOOKING_FOR = "stopped" |
| 767 | for i in range(500): |
| 768 | if LOOKING_FOR in repr(t): |
| 769 | break |
| 770 | time.sleep(0.01) |
| 771 | self.assertIn(LOOKING_FOR, repr(t)) # we waited at least 5 seconds |
Victor Stinner | b8c7be2 | 2017-09-14 13:05:21 -0700 | [diff] [blame] | 772 | t.join() |
Christian Heimes | 1af737c | 2008-01-23 08:24:23 +0000 | [diff] [blame] | 773 | |
Tim Peters | 7634e1c | 2013-10-08 20:55:51 -0500 | [diff] [blame] | 774 | def test_BoundedSemaphore_limit(self): |
Tim Peters | 3d1b7a0 | 2013-10-08 21:29:27 -0500 | [diff] [blame] | 775 | # BoundedSemaphore should raise ValueError if released too often. |
| 776 | for limit in range(1, 10): |
| 777 | bs = threading.BoundedSemaphore(limit) |
| 778 | threads = [threading.Thread(target=bs.acquire) |
| 779 | for _ in range(limit)] |
| 780 | for t in threads: |
| 781 | t.start() |
| 782 | for t in threads: |
| 783 | t.join() |
| 784 | threads = [threading.Thread(target=bs.release) |
| 785 | for _ in range(limit)] |
| 786 | for t in threads: |
| 787 | t.start() |
| 788 | for t in threads: |
| 789 | t.join() |
| 790 | self.assertRaises(ValueError, bs.release) |
Tim Peters | 7634e1c | 2013-10-08 20:55:51 -0500 | [diff] [blame] | 791 | |
Serhiy Storchaka | f28ba36 | 2014-02-07 10:10:55 +0200 | [diff] [blame] | 792 | @cpython_only |
Victor Stinner | fdeb6ec | 2013-12-13 02:01:38 +0100 | [diff] [blame] | 793 | def test_frame_tstate_tracing(self): |
| 794 | # Issue #14432: Crash when a generator is created in a C thread that is |
| 795 | # destroyed while the generator is still used. The issue was that a |
| 796 | # generator contains a frame, and the frame kept a reference to the |
| 797 | # Python state of the destroyed C thread. The crash occurs when a trace |
| 798 | # function is setup. |
| 799 | |
| 800 | def noop_trace(frame, event, arg): |
| 801 | # no operation |
| 802 | return noop_trace |
| 803 | |
| 804 | def generator(): |
| 805 | while 1: |
Berker Peksag | 4882cac | 2015-04-14 09:30:01 +0300 | [diff] [blame] | 806 | yield "generator" |
Victor Stinner | fdeb6ec | 2013-12-13 02:01:38 +0100 | [diff] [blame] | 807 | |
| 808 | def callback(): |
| 809 | if callback.gen is None: |
| 810 | callback.gen = generator() |
| 811 | return next(callback.gen) |
| 812 | callback.gen = None |
| 813 | |
| 814 | old_trace = sys.gettrace() |
| 815 | sys.settrace(noop_trace) |
| 816 | try: |
| 817 | # Install a trace function |
| 818 | threading.settrace(noop_trace) |
| 819 | |
| 820 | # Create a generator in a C thread which exits after the call |
Serhiy Storchaka | f28ba36 | 2014-02-07 10:10:55 +0200 | [diff] [blame] | 821 | import _testcapi |
Victor Stinner | fdeb6ec | 2013-12-13 02:01:38 +0100 | [diff] [blame] | 822 | _testcapi.call_in_temporary_c_thread(callback) |
| 823 | |
| 824 | # Call the generator in a different Python thread, check that the |
| 825 | # generator didn't keep a reference to the destroyed thread state |
| 826 | for test in range(3): |
| 827 | # The trace function is still called here |
| 828 | callback() |
| 829 | finally: |
| 830 | sys.settrace(old_trace) |
| 831 | |
Mario Corchero | 0001a1b | 2020-11-04 10:27:43 +0100 | [diff] [blame] | 832 | def test_gettrace(self): |
| 833 | def noop_trace(frame, event, arg): |
| 834 | # no operation |
| 835 | return noop_trace |
| 836 | old_trace = threading.gettrace() |
| 837 | try: |
| 838 | threading.settrace(noop_trace) |
| 839 | trace_func = threading.gettrace() |
| 840 | self.assertEqual(noop_trace,trace_func) |
| 841 | finally: |
| 842 | threading.settrace(old_trace) |
| 843 | |
| 844 | def test_getprofile(self): |
| 845 | def fn(*args): pass |
| 846 | old_profile = threading.getprofile() |
| 847 | try: |
| 848 | threading.setprofile(fn) |
| 849 | self.assertEqual(fn, threading.getprofile()) |
| 850 | finally: |
| 851 | threading.setprofile(old_profile) |
| 852 | |
Victor Stinner | 6f75c87 | 2019-06-13 12:06:24 +0200 | [diff] [blame] | 853 | @cpython_only |
| 854 | def test_shutdown_locks(self): |
| 855 | for daemon in (False, True): |
| 856 | with self.subTest(daemon=daemon): |
| 857 | event = threading.Event() |
| 858 | thread = threading.Thread(target=event.wait, daemon=daemon) |
| 859 | |
| 860 | # Thread.start() must add lock to _shutdown_locks, |
| 861 | # but only for non-daemon thread |
| 862 | thread.start() |
| 863 | tstate_lock = thread._tstate_lock |
| 864 | if not daemon: |
| 865 | self.assertIn(tstate_lock, threading._shutdown_locks) |
| 866 | else: |
| 867 | self.assertNotIn(tstate_lock, threading._shutdown_locks) |
| 868 | |
| 869 | # unblock the thread and join it |
| 870 | event.set() |
| 871 | thread.join() |
| 872 | |
| 873 | # Thread._stop() must remove tstate_lock from _shutdown_locks. |
| 874 | # Daemon threads must never add it to _shutdown_locks. |
| 875 | self.assertNotIn(tstate_lock, threading._shutdown_locks) |
| 876 | |
Victor Stinner | 9ad58ac | 2020-03-09 23:37:49 +0100 | [diff] [blame] | 877 | def test_locals_at_exit(self): |
| 878 | # bpo-19466: thread locals must not be deleted before destructors |
| 879 | # are called |
| 880 | rc, out, err = assert_python_ok("-c", """if 1: |
| 881 | import threading |
| 882 | |
| 883 | class Atexit: |
| 884 | def __del__(self): |
| 885 | print("thread_dict.atexit = %r" % thread_dict.atexit) |
| 886 | |
| 887 | thread_dict = threading.local() |
| 888 | thread_dict.atexit = "value" |
| 889 | |
| 890 | atexit = Atexit() |
| 891 | """) |
| 892 | self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'value'") |
| 893 | |
BarneyStratford | 01c4fdd | 2021-02-02 20:24:24 +0000 | [diff] [blame] | 894 | def test_boolean_target(self): |
| 895 | # bpo-41149: A thread that had a boolean value of False would not |
| 896 | # run, regardless of whether it was callable. The correct behaviour |
| 897 | # is for a thread to do nothing if its target is None, and to call |
| 898 | # the target otherwise. |
| 899 | class BooleanTarget(object): |
| 900 | def __init__(self): |
| 901 | self.ran = False |
| 902 | def __bool__(self): |
| 903 | return False |
| 904 | def __call__(self): |
| 905 | self.ran = True |
| 906 | |
| 907 | target = BooleanTarget() |
| 908 | thread = threading.Thread(target=target) |
| 909 | thread.start() |
| 910 | thread.join() |
| 911 | self.assertTrue(target.ran) |
| 912 | |
Miss Islington (bot) | 71dca6e | 2021-05-15 02:24:44 -0700 | [diff] [blame] | 913 | def test_leak_without_join(self): |
| 914 | # bpo-37788: Test that a thread which is not joined explicitly |
| 915 | # does not leak. Test written for reference leak checks. |
| 916 | def noop(): pass |
| 917 | with threading_helper.wait_threads_exit(): |
| 918 | threading.Thread(target=noop).start() |
| 919 | # Thread.join() is not called |
BarneyStratford | 01c4fdd | 2021-02-02 20:24:24 +0000 | [diff] [blame] | 920 | |
Miss Islington (bot) | a11158e | 2021-08-06 04:32:37 -0700 | [diff] [blame^] | 921 | @unittest.skipUnless(Py_DEBUG, 'need debug build (Py_DEBUG)') |
| 922 | def test_debug_deprecation(self): |
| 923 | # bpo-44584: The PYTHONTHREADDEBUG environment variable is deprecated |
| 924 | rc, out, err = assert_python_ok("-Wdefault", "-c", "pass", |
| 925 | PYTHONTHREADDEBUG="1") |
| 926 | msg = (b'DeprecationWarning: The threading debug ' |
| 927 | b'(PYTHONTHREADDEBUG environment variable) ' |
| 928 | b'is deprecated and will be removed in Python 3.12') |
| 929 | self.assertIn(msg, err) |
| 930 | |
Victor Stinner | 45956b9 | 2013-11-12 16:37:55 +0100 | [diff] [blame] | 931 | |
Antoine Pitrou | b0e9bd4 | 2009-10-27 20:05:26 +0000 | [diff] [blame] | 932 | class ThreadJoinOnShutdown(BaseTestCase): |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 933 | |
| 934 | def _run_and_join(self, script): |
| 935 | script = """if 1: |
| 936 | import sys, os, time, threading |
| 937 | |
| 938 | # a thread, which waits for the main program to terminate |
| 939 | def joiningfunc(mainthread): |
| 940 | mainthread.join() |
| 941 | print('end of thread') |
Antoine Pitrou | 5fe291f | 2008-09-06 23:00:03 +0000 | [diff] [blame] | 942 | # stdout is fully buffered because not a tty, we have to flush |
| 943 | # before exit. |
| 944 | sys.stdout.flush() |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 945 | \n""" + script |
| 946 | |
Antoine Pitrou | c4d7864 | 2011-05-05 20:17:32 +0200 | [diff] [blame] | 947 | rc, out, err = assert_python_ok("-c", script) |
| 948 | data = out.decode().replace('\r', '') |
Benjamin Peterson | ad703dc | 2008-07-17 17:02:57 +0000 | [diff] [blame] | 949 | self.assertEqual(data, "end of main\nend of thread\n") |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 950 | |
| 951 | def test_1_join_on_shutdown(self): |
| 952 | # The usual case: on exit, wait for a non-daemon thread |
| 953 | script = """if 1: |
| 954 | import os |
| 955 | t = threading.Thread(target=joiningfunc, |
| 956 | args=(threading.current_thread(),)) |
| 957 | t.start() |
| 958 | time.sleep(0.1) |
| 959 | print('end of main') |
| 960 | """ |
| 961 | self._run_and_join(script) |
| 962 | |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 963 | @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") |
Victor Stinner | 26d3186 | 2011-07-01 14:26:24 +0200 | [diff] [blame] | 964 | @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 965 | def test_2_join_in_forked_process(self): |
| 966 | # Like the test above, but from a forked interpreter |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 967 | script = """if 1: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 968 | from test import support |
| 969 | |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 970 | childpid = os.fork() |
| 971 | if childpid != 0: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 972 | # parent process |
| 973 | support.wait_process(childpid, exitcode=0) |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 974 | sys.exit(0) |
| 975 | |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 976 | # child process |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 977 | t = threading.Thread(target=joiningfunc, |
| 978 | args=(threading.current_thread(),)) |
| 979 | t.start() |
| 980 | print('end of main') |
| 981 | """ |
| 982 | self._run_and_join(script) |
| 983 | |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 984 | @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") |
Victor Stinner | 26d3186 | 2011-07-01 14:26:24 +0200 | [diff] [blame] | 985 | @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") |
Antoine Pitrou | 5fe291f | 2008-09-06 23:00:03 +0000 | [diff] [blame] | 986 | def test_3_join_in_forked_from_thread(self): |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 987 | # Like the test above, but fork() was called from a worker thread |
| 988 | # In the forked process, the main Thread object must be marked as stopped. |
Alexandre Vassalotti | 93f2cd2 | 2009-07-22 04:54:52 +0000 | [diff] [blame] | 989 | |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 990 | script = """if 1: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 991 | from test import support |
| 992 | |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 993 | main_thread = threading.current_thread() |
| 994 | def worker(): |
| 995 | childpid = os.fork() |
| 996 | if childpid != 0: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 997 | # parent process |
| 998 | support.wait_process(childpid, exitcode=0) |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 999 | sys.exit(0) |
| 1000 | |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1001 | # child process |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 1002 | t = threading.Thread(target=joiningfunc, |
| 1003 | args=(main_thread,)) |
| 1004 | print('end of main') |
| 1005 | t.start() |
| 1006 | t.join() # Should not block: main_thread is already stopped |
| 1007 | |
| 1008 | w = threading.Thread(target=worker) |
| 1009 | w.start() |
| 1010 | """ |
| 1011 | self._run_and_join(script) |
| 1012 | |
Victor Stinner | 26d3186 | 2011-07-01 14:26:24 +0200 | [diff] [blame] | 1013 | @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") |
Tim Peters | c363a23 | 2013-09-08 18:44:40 -0500 | [diff] [blame] | 1014 | def test_4_daemon_threads(self): |
Antoine Pitrou | 0d5e52d | 2011-05-04 20:02:30 +0200 | [diff] [blame] | 1015 | # Check that a daemon thread cannot crash the interpreter on shutdown |
| 1016 | # by manipulating internal structures that are being disposed of in |
| 1017 | # the main thread. |
| 1018 | script = """if True: |
| 1019 | import os |
| 1020 | import random |
| 1021 | import sys |
| 1022 | import time |
| 1023 | import threading |
| 1024 | |
| 1025 | thread_has_run = set() |
| 1026 | |
| 1027 | def random_io(): |
| 1028 | '''Loop for a while sleeping random tiny amounts and doing some I/O.''' |
Antoine Pitrou | 0d5e52d | 2011-05-04 20:02:30 +0200 | [diff] [blame] | 1029 | while True: |
Serhiy Storchaka | 5b10b98 | 2019-03-05 10:06:26 +0200 | [diff] [blame] | 1030 | with open(os.__file__, 'rb') as in_f: |
| 1031 | stuff = in_f.read(200) |
| 1032 | with open(os.devnull, 'wb') as null_f: |
| 1033 | null_f.write(stuff) |
| 1034 | time.sleep(random.random() / 1995) |
Antoine Pitrou | 0d5e52d | 2011-05-04 20:02:30 +0200 | [diff] [blame] | 1035 | thread_has_run.add(threading.current_thread()) |
| 1036 | |
| 1037 | def main(): |
| 1038 | count = 0 |
| 1039 | for _ in range(40): |
| 1040 | new_thread = threading.Thread(target=random_io) |
| 1041 | new_thread.daemon = True |
| 1042 | new_thread.start() |
| 1043 | count += 1 |
| 1044 | while len(thread_has_run) < count: |
| 1045 | time.sleep(0.001) |
| 1046 | # Trigger process shutdown |
| 1047 | sys.exit(0) |
| 1048 | |
| 1049 | main() |
| 1050 | """ |
| 1051 | rc, out, err = assert_python_ok('-c', script) |
| 1052 | self.assertFalse(err) |
| 1053 | |
Charles-François Natali | 6d0d24e | 2012-02-02 20:31:42 +0100 | [diff] [blame] | 1054 | @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") |
Charles-François Natali | b2c9e9a | 2012-02-08 21:29:11 +0100 | [diff] [blame] | 1055 | @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") |
Charles-François Natali | 6d0d24e | 2012-02-02 20:31:42 +0100 | [diff] [blame] | 1056 | def test_reinit_tls_after_fork(self): |
| 1057 | # Issue #13817: fork() would deadlock in a multithreaded program with |
| 1058 | # the ad-hoc TLS implementation. |
| 1059 | |
| 1060 | def do_fork_and_wait(): |
| 1061 | # just fork a child process and wait it |
| 1062 | pid = os.fork() |
| 1063 | if pid > 0: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1064 | support.wait_process(pid, exitcode=50) |
Charles-François Natali | 6d0d24e | 2012-02-02 20:31:42 +0100 | [diff] [blame] | 1065 | else: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1066 | os._exit(50) |
Charles-François Natali | 6d0d24e | 2012-02-02 20:31:42 +0100 | [diff] [blame] | 1067 | |
| 1068 | # start a bunch of threads that will fork() child processes |
| 1069 | threads = [] |
| 1070 | for i in range(16): |
| 1071 | t = threading.Thread(target=do_fork_and_wait) |
| 1072 | threads.append(t) |
| 1073 | t.start() |
| 1074 | |
| 1075 | for t in threads: |
| 1076 | t.join() |
| 1077 | |
Antoine Pitrou | 8408cea | 2013-05-05 23:47:09 +0200 | [diff] [blame] | 1078 | @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") |
| 1079 | def test_clear_threads_states_after_fork(self): |
| 1080 | # Issue #17094: check that threads states are cleared after fork() |
| 1081 | |
| 1082 | # start a bunch of threads |
| 1083 | threads = [] |
| 1084 | for i in range(16): |
| 1085 | t = threading.Thread(target=lambda : time.sleep(0.3)) |
| 1086 | threads.append(t) |
| 1087 | t.start() |
| 1088 | |
| 1089 | pid = os.fork() |
| 1090 | if pid == 0: |
| 1091 | # check that threads states have been cleared |
| 1092 | if len(sys._current_frames()) == 1: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1093 | os._exit(51) |
Antoine Pitrou | 8408cea | 2013-05-05 23:47:09 +0200 | [diff] [blame] | 1094 | else: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1095 | os._exit(52) |
Antoine Pitrou | 8408cea | 2013-05-05 23:47:09 +0200 | [diff] [blame] | 1096 | else: |
Victor Stinner | a9f9687 | 2020-03-31 21:49:44 +0200 | [diff] [blame] | 1097 | support.wait_process(pid, exitcode=51) |
Antoine Pitrou | 8408cea | 2013-05-05 23:47:09 +0200 | [diff] [blame] | 1098 | |
| 1099 | for t in threads: |
| 1100 | t.join() |
| 1101 | |
Jesse Noller | a851397 | 2008-07-17 16:49:17 +0000 | [diff] [blame] | 1102 | |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1103 | class SubinterpThreadingTests(BaseTestCase): |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 1104 | def pipe(self): |
| 1105 | r, w = os.pipe() |
| 1106 | self.addCleanup(os.close, r) |
| 1107 | self.addCleanup(os.close, w) |
| 1108 | if hasattr(os, 'set_blocking'): |
| 1109 | os.set_blocking(r, False) |
| 1110 | return (r, w) |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1111 | |
| 1112 | def test_threads_join(self): |
| 1113 | # Non-daemon threads should be joined at subinterpreter shutdown |
| 1114 | # (issue #18808) |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 1115 | r, w = self.pipe() |
| 1116 | code = textwrap.dedent(r""" |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1117 | import os |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1118 | import random |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1119 | import threading |
| 1120 | import time |
| 1121 | |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1122 | def random_sleep(): |
| 1123 | seconds = random.random() * 0.010 |
| 1124 | time.sleep(seconds) |
| 1125 | |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1126 | def f(): |
| 1127 | # Sleep a bit so that the thread is still running when |
| 1128 | # Py_EndInterpreter is called. |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1129 | random_sleep() |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1130 | os.write(%d, b"x") |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1131 | |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1132 | threading.Thread(target=f).start() |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1133 | random_sleep() |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 1134 | """ % (w,)) |
Victor Stinner | ed3b0bc | 2013-11-23 12:27:24 +0100 | [diff] [blame] | 1135 | ret = test.support.run_in_subinterp(code) |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1136 | self.assertEqual(ret, 0) |
| 1137 | # The thread was joined properly. |
| 1138 | self.assertEqual(os.read(r, 1), b"x") |
| 1139 | |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1140 | def test_threads_join_2(self): |
| 1141 | # Same as above, but a delay gets introduced after the thread's |
| 1142 | # Python code returned but before the thread state is deleted. |
| 1143 | # To achieve this, we register a thread-local object which sleeps |
| 1144 | # a bit when deallocated. |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 1145 | r, w = self.pipe() |
| 1146 | code = textwrap.dedent(r""" |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1147 | import os |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1148 | import random |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1149 | import threading |
| 1150 | import time |
| 1151 | |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1152 | def random_sleep(): |
| 1153 | seconds = random.random() * 0.010 |
| 1154 | time.sleep(seconds) |
| 1155 | |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1156 | class Sleeper: |
| 1157 | def __del__(self): |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1158 | random_sleep() |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1159 | |
| 1160 | tls = threading.local() |
| 1161 | |
| 1162 | def f(): |
| 1163 | # Sleep a bit so that the thread is still running when |
| 1164 | # Py_EndInterpreter is called. |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1165 | random_sleep() |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1166 | tls.x = Sleeper() |
| 1167 | os.write(%d, b"x") |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1168 | |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1169 | threading.Thread(target=f).start() |
Victor Stinner | 468e5fe | 2019-06-13 01:30:17 +0200 | [diff] [blame] | 1170 | random_sleep() |
Victor Stinner | 066e5b1 | 2019-06-14 18:55:22 +0200 | [diff] [blame] | 1171 | """ % (w,)) |
Victor Stinner | ed3b0bc | 2013-11-23 12:27:24 +0100 | [diff] [blame] | 1172 | ret = test.support.run_in_subinterp(code) |
Antoine Pitrou | 7b47699 | 2013-09-07 23:38:37 +0200 | [diff] [blame] | 1173 | self.assertEqual(ret, 0) |
| 1174 | # The thread was joined properly. |
| 1175 | self.assertEqual(os.read(r, 1), b"x") |
| 1176 | |
Victor Stinner | 14d5331 | 2020-04-12 23:45:09 +0200 | [diff] [blame] | 1177 | @cpython_only |
| 1178 | def test_daemon_threads_fatal_error(self): |
| 1179 | subinterp_code = f"""if 1: |
| 1180 | import os |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1181 | import threading |
Victor Stinner | 14d5331 | 2020-04-12 23:45:09 +0200 | [diff] [blame] | 1182 | import time |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1183 | |
Victor Stinner | 14d5331 | 2020-04-12 23:45:09 +0200 | [diff] [blame] | 1184 | def f(): |
| 1185 | # Make sure the daemon thread is still running when |
| 1186 | # Py_EndInterpreter is called. |
| 1187 | time.sleep({test.support.SHORT_TIMEOUT}) |
| 1188 | threading.Thread(target=f, daemon=True).start() |
| 1189 | """ |
| 1190 | script = r"""if 1: |
| 1191 | import _testcapi |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1192 | |
Victor Stinner | 14d5331 | 2020-04-12 23:45:09 +0200 | [diff] [blame] | 1193 | _testcapi.run_in_subinterp(%r) |
| 1194 | """ % (subinterp_code,) |
| 1195 | with test.support.SuppressCrashReport(): |
| 1196 | rc, out, err = assert_python_failure("-c", script) |
| 1197 | self.assertIn("Fatal Python error: Py_EndInterpreter: " |
| 1198 | "not the last thread", err.decode()) |
Antoine Pitrou | 7eaf3f7 | 2013-08-25 19:48:18 +0200 | [diff] [blame] | 1199 | |
| 1200 | |
Antoine Pitrou | b0e9bd4 | 2009-10-27 20:05:26 +0000 | [diff] [blame] | 1201 | class ThreadingExceptionTests(BaseTestCase): |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1202 | # A RuntimeError should be raised if Thread.start() is called |
| 1203 | # multiple times. |
| 1204 | def test_start_thread_again(self): |
| 1205 | thread = threading.Thread() |
| 1206 | thread.start() |
| 1207 | self.assertRaises(RuntimeError, thread.start) |
Victor Stinner | b8c7be2 | 2017-09-14 13:05:21 -0700 | [diff] [blame] | 1208 | thread.join() |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1209 | |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1210 | def test_joining_current_thread(self): |
Benjamin Peterson | 672b803 | 2008-06-11 19:14:14 +0000 | [diff] [blame] | 1211 | current_thread = threading.current_thread() |
| 1212 | self.assertRaises(RuntimeError, current_thread.join); |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1213 | |
| 1214 | def test_joining_inactive_thread(self): |
| 1215 | thread = threading.Thread() |
| 1216 | self.assertRaises(RuntimeError, thread.join) |
| 1217 | |
| 1218 | def test_daemonize_active_thread(self): |
| 1219 | thread = threading.Thread() |
| 1220 | thread.start() |
Benjamin Peterson | fdbea96 | 2008-08-18 17:33:47 +0000 | [diff] [blame] | 1221 | self.assertRaises(RuntimeError, setattr, thread, "daemon", True) |
Victor Stinner | b8c7be2 | 2017-09-14 13:05:21 -0700 | [diff] [blame] | 1222 | thread.join() |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1223 | |
Antoine Pitrou | fcf81fd | 2011-02-28 22:03:34 +0000 | [diff] [blame] | 1224 | def test_releasing_unacquired_lock(self): |
| 1225 | lock = threading.Lock() |
| 1226 | self.assertRaises(RuntimeError, lock.release) |
| 1227 | |
Ned Deily | 9a7c524 | 2011-05-28 00:19:56 -0700 | [diff] [blame] | 1228 | def test_recursion_limit(self): |
| 1229 | # Issue 9670 |
| 1230 | # test that excessive recursion within a non-main thread causes |
| 1231 | # an exception rather than crashing the interpreter on platforms |
| 1232 | # like Mac OS X or FreeBSD which have small default stack sizes |
| 1233 | # for threads |
| 1234 | script = """if True: |
| 1235 | import threading |
| 1236 | |
| 1237 | def recurse(): |
| 1238 | return recurse() |
| 1239 | |
| 1240 | def outer(): |
| 1241 | try: |
| 1242 | recurse() |
Yury Selivanov | f488fb4 | 2015-07-03 01:04:23 -0400 | [diff] [blame] | 1243 | except RecursionError: |
Ned Deily | 9a7c524 | 2011-05-28 00:19:56 -0700 | [diff] [blame] | 1244 | pass |
| 1245 | |
| 1246 | w = threading.Thread(target=outer) |
| 1247 | w.start() |
| 1248 | w.join() |
| 1249 | print('end of main thread') |
| 1250 | """ |
| 1251 | expected_output = "end of main thread\n" |
| 1252 | p = subprocess.Popen([sys.executable, "-c", script], |
Antoine Pitrou | b8b6a68 | 2012-06-29 19:40:35 +0200 | [diff] [blame] | 1253 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
Ned Deily | 9a7c524 | 2011-05-28 00:19:56 -0700 | [diff] [blame] | 1254 | stdout, stderr = p.communicate() |
| 1255 | data = stdout.decode().replace('\r', '') |
Antoine Pitrou | b8b6a68 | 2012-06-29 19:40:35 +0200 | [diff] [blame] | 1256 | self.assertEqual(p.returncode, 0, "Unexpected error: " + stderr.decode()) |
Ned Deily | 9a7c524 | 2011-05-28 00:19:56 -0700 | [diff] [blame] | 1257 | self.assertEqual(data, expected_output) |
Guido van Rossum | cd16bf6 | 2007-06-13 18:07:49 +0000 | [diff] [blame] | 1258 | |
Serhiy Storchaka | 52005c2 | 2014-09-21 22:08:13 +0300 | [diff] [blame] | 1259 | def test_print_exception(self): |
| 1260 | script = r"""if True: |
| 1261 | import threading |
| 1262 | import time |
| 1263 | |
| 1264 | running = False |
| 1265 | def run(): |
| 1266 | global running |
| 1267 | running = True |
| 1268 | while running: |
| 1269 | time.sleep(0.01) |
| 1270 | 1/0 |
| 1271 | t = threading.Thread(target=run) |
| 1272 | t.start() |
| 1273 | while not running: |
| 1274 | time.sleep(0.01) |
| 1275 | running = False |
| 1276 | t.join() |
| 1277 | """ |
| 1278 | rc, out, err = assert_python_ok("-c", script) |
| 1279 | self.assertEqual(out, b'') |
| 1280 | err = err.decode() |
| 1281 | self.assertIn("Exception in thread", err) |
| 1282 | self.assertIn("Traceback (most recent call last):", err) |
| 1283 | self.assertIn("ZeroDivisionError", err) |
| 1284 | self.assertNotIn("Unhandled exception", err) |
| 1285 | |
| 1286 | def test_print_exception_stderr_is_none_1(self): |
| 1287 | script = r"""if True: |
| 1288 | import sys |
| 1289 | import threading |
| 1290 | import time |
| 1291 | |
| 1292 | running = False |
| 1293 | def run(): |
| 1294 | global running |
| 1295 | running = True |
| 1296 | while running: |
| 1297 | time.sleep(0.01) |
| 1298 | 1/0 |
| 1299 | t = threading.Thread(target=run) |
| 1300 | t.start() |
| 1301 | while not running: |
| 1302 | time.sleep(0.01) |
| 1303 | sys.stderr = None |
| 1304 | running = False |
| 1305 | t.join() |
| 1306 | """ |
| 1307 | rc, out, err = assert_python_ok("-c", script) |
| 1308 | self.assertEqual(out, b'') |
| 1309 | err = err.decode() |
| 1310 | self.assertIn("Exception in thread", err) |
| 1311 | self.assertIn("Traceback (most recent call last):", err) |
| 1312 | self.assertIn("ZeroDivisionError", err) |
| 1313 | self.assertNotIn("Unhandled exception", err) |
| 1314 | |
| 1315 | def test_print_exception_stderr_is_none_2(self): |
| 1316 | script = r"""if True: |
| 1317 | import sys |
| 1318 | import threading |
| 1319 | import time |
| 1320 | |
| 1321 | running = False |
| 1322 | def run(): |
| 1323 | global running |
| 1324 | running = True |
| 1325 | while running: |
| 1326 | time.sleep(0.01) |
| 1327 | 1/0 |
| 1328 | sys.stderr = None |
| 1329 | t = threading.Thread(target=run) |
| 1330 | t.start() |
| 1331 | while not running: |
| 1332 | time.sleep(0.01) |
| 1333 | running = False |
| 1334 | t.join() |
| 1335 | """ |
| 1336 | rc, out, err = assert_python_ok("-c", script) |
| 1337 | self.assertEqual(out, b'') |
| 1338 | self.assertNotIn("Unhandled exception", err.decode()) |
| 1339 | |
Victor Stinner | eec9331 | 2016-08-18 18:13:10 +0200 | [diff] [blame] | 1340 | def test_bare_raise_in_brand_new_thread(self): |
| 1341 | def bare_raise(): |
| 1342 | raise |
| 1343 | |
| 1344 | class Issue27558(threading.Thread): |
| 1345 | exc = None |
| 1346 | |
| 1347 | def run(self): |
| 1348 | try: |
| 1349 | bare_raise() |
| 1350 | except Exception as exc: |
| 1351 | self.exc = exc |
| 1352 | |
| 1353 | thread = Issue27558() |
| 1354 | thread.start() |
| 1355 | thread.join() |
| 1356 | self.assertIsNotNone(thread.exc) |
| 1357 | self.assertIsInstance(thread.exc, RuntimeError) |
Victor Stinner | 3d284c0 | 2017-08-19 01:54:42 +0200 | [diff] [blame] | 1358 | # explicitly break the reference cycle to not leak a dangling thread |
| 1359 | thread.exc = None |
Serhiy Storchaka | 52005c2 | 2014-09-21 22:08:13 +0300 | [diff] [blame] | 1360 | |
Irit Katriel | 373741a | 2021-05-18 14:53:57 +0100 | [diff] [blame] | 1361 | def test_multithread_modify_file_noerror(self): |
| 1362 | # See issue25872 |
| 1363 | def modify_file(): |
| 1364 | with open(os_helper.TESTFN, 'w', encoding='utf-8') as fp: |
| 1365 | fp.write(' ') |
| 1366 | traceback.format_stack() |
| 1367 | |
| 1368 | self.addCleanup(os_helper.unlink, os_helper.TESTFN) |
| 1369 | threads = [ |
| 1370 | threading.Thread(target=modify_file) |
| 1371 | for i in range(100) |
| 1372 | ] |
| 1373 | for t in threads: |
| 1374 | t.start() |
| 1375 | t.join() |
| 1376 | |
Victor Stinner | cd590a7 | 2019-05-28 00:39:52 +0200 | [diff] [blame] | 1377 | |
| 1378 | class ThreadRunFail(threading.Thread): |
| 1379 | def run(self): |
| 1380 | raise ValueError("run failed") |
| 1381 | |
| 1382 | |
| 1383 | class ExceptHookTests(BaseTestCase): |
Victor Stinner | b136b1a | 2021-04-16 14:33:10 +0200 | [diff] [blame] | 1384 | def setUp(self): |
| 1385 | restore_default_excepthook(self) |
| 1386 | super().setUp() |
| 1387 | |
Victor Stinner | cd590a7 | 2019-05-28 00:39:52 +0200 | [diff] [blame] | 1388 | def test_excepthook(self): |
| 1389 | with support.captured_output("stderr") as stderr: |
| 1390 | thread = ThreadRunFail(name="excepthook thread") |
| 1391 | thread.start() |
| 1392 | thread.join() |
| 1393 | |
| 1394 | stderr = stderr.getvalue().strip() |
| 1395 | self.assertIn(f'Exception in thread {thread.name}:\n', stderr) |
| 1396 | self.assertIn('Traceback (most recent call last):\n', stderr) |
| 1397 | self.assertIn(' raise ValueError("run failed")', stderr) |
| 1398 | self.assertIn('ValueError: run failed', stderr) |
| 1399 | |
| 1400 | @support.cpython_only |
| 1401 | def test_excepthook_thread_None(self): |
| 1402 | # threading.excepthook called with thread=None: log the thread |
| 1403 | # identifier in this case. |
| 1404 | with support.captured_output("stderr") as stderr: |
| 1405 | try: |
| 1406 | raise ValueError("bug") |
| 1407 | except Exception as exc: |
| 1408 | args = threading.ExceptHookArgs([*sys.exc_info(), None]) |
Victor Stinner | cdce057 | 2019-06-02 23:08:41 +0200 | [diff] [blame] | 1409 | try: |
| 1410 | threading.excepthook(args) |
| 1411 | finally: |
| 1412 | # Explicitly break a reference cycle |
| 1413 | args = None |
Victor Stinner | cd590a7 | 2019-05-28 00:39:52 +0200 | [diff] [blame] | 1414 | |
| 1415 | stderr = stderr.getvalue().strip() |
| 1416 | self.assertIn(f'Exception in thread {threading.get_ident()}:\n', stderr) |
| 1417 | self.assertIn('Traceback (most recent call last):\n', stderr) |
| 1418 | self.assertIn(' raise ValueError("bug")', stderr) |
| 1419 | self.assertIn('ValueError: bug', stderr) |
| 1420 | |
| 1421 | def test_system_exit(self): |
| 1422 | class ThreadExit(threading.Thread): |
| 1423 | def run(self): |
| 1424 | sys.exit(1) |
| 1425 | |
| 1426 | # threading.excepthook() silently ignores SystemExit |
| 1427 | with support.captured_output("stderr") as stderr: |
| 1428 | thread = ThreadExit() |
| 1429 | thread.start() |
| 1430 | thread.join() |
| 1431 | |
| 1432 | self.assertEqual(stderr.getvalue(), '') |
| 1433 | |
| 1434 | def test_custom_excepthook(self): |
| 1435 | args = None |
| 1436 | |
| 1437 | def hook(hook_args): |
| 1438 | nonlocal args |
| 1439 | args = hook_args |
| 1440 | |
| 1441 | try: |
| 1442 | with support.swap_attr(threading, 'excepthook', hook): |
| 1443 | thread = ThreadRunFail() |
| 1444 | thread.start() |
| 1445 | thread.join() |
| 1446 | |
| 1447 | self.assertEqual(args.exc_type, ValueError) |
| 1448 | self.assertEqual(str(args.exc_value), 'run failed') |
| 1449 | self.assertEqual(args.exc_traceback, args.exc_value.__traceback__) |
| 1450 | self.assertIs(args.thread, thread) |
| 1451 | finally: |
| 1452 | # Break reference cycle |
| 1453 | args = None |
| 1454 | |
| 1455 | def test_custom_excepthook_fail(self): |
| 1456 | def threading_hook(args): |
| 1457 | raise ValueError("threading_hook failed") |
| 1458 | |
| 1459 | err_str = None |
| 1460 | |
| 1461 | def sys_hook(exc_type, exc_value, exc_traceback): |
| 1462 | nonlocal err_str |
| 1463 | err_str = str(exc_value) |
| 1464 | |
| 1465 | with support.swap_attr(threading, 'excepthook', threading_hook), \ |
| 1466 | support.swap_attr(sys, 'excepthook', sys_hook), \ |
| 1467 | support.captured_output('stderr') as stderr: |
| 1468 | thread = ThreadRunFail() |
| 1469 | thread.start() |
| 1470 | thread.join() |
| 1471 | |
| 1472 | self.assertEqual(stderr.getvalue(), |
| 1473 | 'Exception in threading.excepthook:\n') |
| 1474 | self.assertEqual(err_str, 'threading_hook failed') |
| 1475 | |
Mario Corchero | 750c5ab | 2020-11-12 18:27:44 +0100 | [diff] [blame] | 1476 | def test_original_excepthook(self): |
| 1477 | def run_thread(): |
| 1478 | with support.captured_output("stderr") as output: |
| 1479 | thread = ThreadRunFail(name="excepthook thread") |
| 1480 | thread.start() |
| 1481 | thread.join() |
| 1482 | return output.getvalue() |
| 1483 | |
| 1484 | def threading_hook(args): |
| 1485 | print("Running a thread failed", file=sys.stderr) |
| 1486 | |
| 1487 | default_output = run_thread() |
| 1488 | with support.swap_attr(threading, 'excepthook', threading_hook): |
| 1489 | custom_hook_output = run_thread() |
| 1490 | threading.excepthook = threading.__excepthook__ |
| 1491 | recovered_output = run_thread() |
| 1492 | |
| 1493 | self.assertEqual(default_output, recovered_output) |
| 1494 | self.assertNotEqual(default_output, custom_hook_output) |
| 1495 | self.assertEqual(custom_hook_output, "Running a thread failed\n") |
| 1496 | |
Victor Stinner | cd590a7 | 2019-05-28 00:39:52 +0200 | [diff] [blame] | 1497 | |
R David Murray | 19aeb43 | 2013-03-30 17:19:38 -0400 | [diff] [blame] | 1498 | class TimerTests(BaseTestCase): |
| 1499 | |
| 1500 | def setUp(self): |
| 1501 | BaseTestCase.setUp(self) |
| 1502 | self.callback_args = [] |
| 1503 | self.callback_event = threading.Event() |
| 1504 | |
| 1505 | def test_init_immutable_default_args(self): |
| 1506 | # Issue 17435: constructor defaults were mutable objects, they could be |
| 1507 | # mutated via the object attributes and affect other Timer objects. |
| 1508 | timer1 = threading.Timer(0.01, self._callback_spy) |
| 1509 | timer1.start() |
| 1510 | self.callback_event.wait() |
| 1511 | timer1.args.append("blah") |
| 1512 | timer1.kwargs["foo"] = "bar" |
| 1513 | self.callback_event.clear() |
| 1514 | timer2 = threading.Timer(0.01, self._callback_spy) |
| 1515 | timer2.start() |
| 1516 | self.callback_event.wait() |
| 1517 | self.assertEqual(len(self.callback_args), 2) |
| 1518 | self.assertEqual(self.callback_args, [((), {}), ((), {})]) |
Victor Stinner | da3e5cf | 2017-09-15 05:37:42 -0700 | [diff] [blame] | 1519 | timer1.join() |
| 1520 | timer2.join() |
R David Murray | 19aeb43 | 2013-03-30 17:19:38 -0400 | [diff] [blame] | 1521 | |
| 1522 | def _callback_spy(self, *args, **kwargs): |
| 1523 | self.callback_args.append((args[:], kwargs.copy())) |
| 1524 | self.callback_event.set() |
| 1525 | |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 1526 | class LockTests(lock_tests.LockTests): |
| 1527 | locktype = staticmethod(threading.Lock) |
| 1528 | |
Antoine Pitrou | 434736a | 2009-11-10 18:46:01 +0000 | [diff] [blame] | 1529 | class PyRLockTests(lock_tests.RLockTests): |
| 1530 | locktype = staticmethod(threading._PyRLock) |
| 1531 | |
Charles-François Natali | 6b671b2 | 2012-01-28 11:36:04 +0100 | [diff] [blame] | 1532 | @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C') |
Antoine Pitrou | 434736a | 2009-11-10 18:46:01 +0000 | [diff] [blame] | 1533 | class CRLockTests(lock_tests.RLockTests): |
| 1534 | locktype = staticmethod(threading._CRLock) |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 1535 | |
| 1536 | class EventTests(lock_tests.EventTests): |
| 1537 | eventtype = staticmethod(threading.Event) |
| 1538 | |
| 1539 | class ConditionAsRLockTests(lock_tests.RLockTests): |
Serhiy Storchaka | 6a7b3a7 | 2016-04-17 08:32:47 +0300 | [diff] [blame] | 1540 | # Condition uses an RLock by default and exports its API. |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 1541 | locktype = staticmethod(threading.Condition) |
| 1542 | |
| 1543 | class ConditionTests(lock_tests.ConditionTests): |
| 1544 | condtype = staticmethod(threading.Condition) |
| 1545 | |
| 1546 | class SemaphoreTests(lock_tests.SemaphoreTests): |
| 1547 | semtype = staticmethod(threading.Semaphore) |
| 1548 | |
| 1549 | class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests): |
| 1550 | semtype = staticmethod(threading.BoundedSemaphore) |
| 1551 | |
Kristján Valur Jónsson | 3be0003 | 2010-10-28 09:43:10 +0000 | [diff] [blame] | 1552 | class BarrierTests(lock_tests.BarrierTests): |
| 1553 | barriertype = staticmethod(threading.Barrier) |
Antoine Pitrou | 557934f | 2009-11-06 22:41:14 +0000 | [diff] [blame] | 1554 | |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1555 | |
Martin Panter | 19e69c5 | 2015-11-14 12:46:42 +0000 | [diff] [blame] | 1556 | class MiscTestCase(unittest.TestCase): |
| 1557 | def test__all__(self): |
Victor Stinner | b136b1a | 2021-04-16 14:33:10 +0200 | [diff] [blame] | 1558 | restore_default_excepthook(self) |
| 1559 | |
Martin Panter | 19e69c5 | 2015-11-14 12:46:42 +0000 | [diff] [blame] | 1560 | extra = {"ThreadError"} |
Victor Stinner | fbf43f0 | 2020-08-17 07:20:40 +0200 | [diff] [blame] | 1561 | not_exported = {'currentThread', 'activeCount'} |
Martin Panter | 19e69c5 | 2015-11-14 12:46:42 +0000 | [diff] [blame] | 1562 | support.check__all__(self, threading, ('threading', '_thread'), |
Victor Stinner | fbf43f0 | 2020-08-17 07:20:40 +0200 | [diff] [blame] | 1563 | extra=extra, not_exported=not_exported) |
Martin Panter | 19e69c5 | 2015-11-14 12:46:42 +0000 | [diff] [blame] | 1564 | |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1565 | |
| 1566 | class InterruptMainTests(unittest.TestCase): |
Antoine Pitrou | ba251c2 | 2021-03-11 23:35:45 +0100 | [diff] [blame] | 1567 | def check_interrupt_main_with_signal_handler(self, signum): |
| 1568 | def handler(signum, frame): |
| 1569 | 1/0 |
| 1570 | |
| 1571 | old_handler = signal.signal(signum, handler) |
| 1572 | self.addCleanup(signal.signal, signum, old_handler) |
| 1573 | |
| 1574 | with self.assertRaises(ZeroDivisionError): |
| 1575 | _thread.interrupt_main() |
| 1576 | |
| 1577 | def check_interrupt_main_noerror(self, signum): |
| 1578 | handler = signal.getsignal(signum) |
| 1579 | try: |
| 1580 | # No exception should arise. |
| 1581 | signal.signal(signum, signal.SIG_IGN) |
| 1582 | _thread.interrupt_main(signum) |
| 1583 | |
| 1584 | signal.signal(signum, signal.SIG_DFL) |
| 1585 | _thread.interrupt_main(signum) |
| 1586 | finally: |
| 1587 | # Restore original handler |
| 1588 | signal.signal(signum, handler) |
| 1589 | |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1590 | def test_interrupt_main_subthread(self): |
| 1591 | # Calling start_new_thread with a function that executes interrupt_main |
| 1592 | # should raise KeyboardInterrupt upon completion. |
| 1593 | def call_interrupt(): |
| 1594 | _thread.interrupt_main() |
| 1595 | t = threading.Thread(target=call_interrupt) |
| 1596 | with self.assertRaises(KeyboardInterrupt): |
| 1597 | t.start() |
| 1598 | t.join() |
| 1599 | t.join() |
| 1600 | |
| 1601 | def test_interrupt_main_mainthread(self): |
| 1602 | # Make sure that if interrupt_main is called in main thread that |
| 1603 | # KeyboardInterrupt is raised instantly. |
| 1604 | with self.assertRaises(KeyboardInterrupt): |
| 1605 | _thread.interrupt_main() |
| 1606 | |
Antoine Pitrou | ba251c2 | 2021-03-11 23:35:45 +0100 | [diff] [blame] | 1607 | def test_interrupt_main_with_signal_handler(self): |
| 1608 | self.check_interrupt_main_with_signal_handler(signal.SIGINT) |
| 1609 | self.check_interrupt_main_with_signal_handler(signal.SIGTERM) |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1610 | |
Antoine Pitrou | ba251c2 | 2021-03-11 23:35:45 +0100 | [diff] [blame] | 1611 | def test_interrupt_main_noerror(self): |
| 1612 | self.check_interrupt_main_noerror(signal.SIGINT) |
| 1613 | self.check_interrupt_main_noerror(signal.SIGTERM) |
| 1614 | |
| 1615 | def test_interrupt_main_invalid_signal(self): |
| 1616 | self.assertRaises(ValueError, _thread.interrupt_main, -1) |
| 1617 | self.assertRaises(ValueError, _thread.interrupt_main, signal.NSIG) |
| 1618 | self.assertRaises(ValueError, _thread.interrupt_main, 1000000) |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1619 | |
Miss Islington (bot) | 37bdd22 | 2021-07-19 04:15:58 -0700 | [diff] [blame] | 1620 | @threading_helper.reap_threads |
| 1621 | def test_can_interrupt_tight_loops(self): |
| 1622 | cont = [True] |
| 1623 | started = [False] |
| 1624 | interrupted = [False] |
| 1625 | |
| 1626 | def worker(started, cont, interrupted): |
| 1627 | iterations = 100_000_000 |
| 1628 | started[0] = True |
| 1629 | while cont[0]: |
| 1630 | if iterations: |
| 1631 | iterations -= 1 |
| 1632 | else: |
| 1633 | return |
| 1634 | pass |
| 1635 | interrupted[0] = True |
| 1636 | |
| 1637 | t = threading.Thread(target=worker,args=(started, cont, interrupted)) |
| 1638 | t.start() |
| 1639 | while not started[0]: |
| 1640 | pass |
| 1641 | cont[0] = False |
| 1642 | t.join() |
| 1643 | self.assertTrue(interrupted[0]) |
| 1644 | |
Matěj Cepl | 608876b | 2019-05-23 22:30:00 +0200 | [diff] [blame] | 1645 | |
Kyle Stanley | b61b818 | 2020-03-27 15:31:22 -0400 | [diff] [blame] | 1646 | class AtexitTests(unittest.TestCase): |
| 1647 | |
| 1648 | def test_atexit_output(self): |
| 1649 | rc, out, err = assert_python_ok("-c", """if True: |
| 1650 | import threading |
| 1651 | |
| 1652 | def run_last(): |
| 1653 | print('parrot') |
| 1654 | |
| 1655 | threading._register_atexit(run_last) |
| 1656 | """) |
| 1657 | |
| 1658 | self.assertFalse(err) |
| 1659 | self.assertEqual(out.strip(), b'parrot') |
| 1660 | |
| 1661 | def test_atexit_called_once(self): |
| 1662 | rc, out, err = assert_python_ok("-c", """if True: |
| 1663 | import threading |
| 1664 | from unittest.mock import Mock |
| 1665 | |
| 1666 | mock = Mock() |
| 1667 | threading._register_atexit(mock) |
| 1668 | mock.assert_not_called() |
| 1669 | # force early shutdown to ensure it was called once |
| 1670 | threading._shutdown() |
| 1671 | mock.assert_called_once() |
| 1672 | """) |
| 1673 | |
| 1674 | self.assertFalse(err) |
| 1675 | |
| 1676 | def test_atexit_after_shutdown(self): |
| 1677 | # The only way to do this is by registering an atexit within |
| 1678 | # an atexit, which is intended to raise an exception. |
| 1679 | rc, out, err = assert_python_ok("-c", """if True: |
| 1680 | import threading |
| 1681 | |
| 1682 | def func(): |
| 1683 | pass |
| 1684 | |
| 1685 | def run_last(): |
| 1686 | threading._register_atexit(func) |
| 1687 | |
| 1688 | threading._register_atexit(run_last) |
| 1689 | """) |
| 1690 | |
| 1691 | self.assertTrue(err) |
| 1692 | self.assertIn("RuntimeError: can't register atexit after shutdown", |
| 1693 | err.decode()) |
| 1694 | |
| 1695 | |
Tim Peters | 84d5489 | 2005-01-08 06:03:17 +0000 | [diff] [blame] | 1696 | if __name__ == "__main__": |
R David Murray | 19aeb43 | 2013-03-30 17:19:38 -0400 | [diff] [blame] | 1697 | unittest.main() |