def __init__(self, f, n, wait_before_exit=False): """ Construct a bunch of `n` threads running the same function `f`. If `wait_before_exit` is True, the threads won't terminate until do_finish() is called. """ self.f = f self.n = n self.started = [] self.finished = [] self._can_exit = not wait_before_exit self.wait_thread = support.wait_threads_exit() self.wait_thread.__enter__() def task(): tid = threading.get_ident() self.started.append(tid) try: f() finally: self.finished.append(tid) while not self._can_exit: _wait() try: for i in range(n): start_new_thread(task, ()) except: self._can_exit = True raise
def test_rlock_acquire_interruption(self): # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck # in a deadlock. # XXX this test can fail when the legacy (non-semaphore) implementation # of locks is used in thread_pthread.h, see issue #11223. oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) try: rlock = thread.RLock() # For reentrant locks, the initial acquisition must be in another # thread. def other_thread(): rlock.acquire() with support.wait_threads_exit(): thread.start_new_thread(other_thread, ()) # Wait until we can't acquire it without blocking... while rlock.acquire(blocking=False): rlock.release() time.sleep(0.01) signal.alarm(1) t1 = time.monotonic() self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5) dt = time.monotonic() - t1 # See rationale above in test_lock_acquire_interruption self.assertLess(dt, 3.0) finally: signal.alarm(0) signal.signal(signal.SIGALRM, oldalrm)
def test__count(self): # Test the _count() function. orig = thread._count() mut = thread.allocate_lock() mut.acquire() started = [] def task(): started.append(None) mut.acquire() mut.release() with support.wait_threads_exit(): thread.start_new_thread(task, ()) while not started: time.sleep(0.01) self.assertEqual(thread._count(), orig + 1) # Allow the task to finish. mut.release() # The only reliable way to be sure that the thread ended from the # interpreter's point of view is to wait for the function object to be # destroyed. done = [] wr = weakref.ref(task, lambda _: done.append(None)) del task while not done: time.sleep(0.01) # pypy: explicitly collect garbage support.gc_collect() self.assertEqual(thread._count(), orig)
def acquire_retries_on_intr(self, lock): self.sig_recvd = False def my_handler(signal, frame): self.sig_recvd = True old_handler = signal.signal(signal.SIGUSR1, my_handler) try: def other_thread(): # Acquire the lock in a non-main thread, so this test works for # RLocks. lock.acquire() # Wait until the main thread is blocked in the lock acquire, and # then wake it up with this. time.sleep(0.5) os.kill(process_pid, signal.SIGUSR1) # Let the main thread take the interrupt, handle it, and retry # the lock acquisition. Then we'll let it run. time.sleep(0.5) lock.release() with support.wait_threads_exit(): thread.start_new_thread(other_thread, ()) # Wait until we can't acquire it without blocking... while lock.acquire(blocking=False): lock.release() time.sleep(0.01) result = lock.acquire() # Block while we receive a signal. self.assertTrue(self.sig_recvd) self.assertTrue(result) finally: signal.signal(signal.SIGUSR1, old_handler)
def test__count(self): # Test the _count() function. orig = thread._count() mut = thread.allocate_lock() mut.acquire() started = [] def task(): started.append(None) mut.acquire() mut.release() with support.wait_threads_exit(): thread.start_new_thread(task, ()) while not started: time.sleep(POLL_SLEEP) self.assertEqual(thread._count(), orig + 1) # Allow the task to finish. mut.release() # The only reliable way to be sure that the thread ended from the # interpreter's point of view is to wait for the function object to be # destroyed. done = [] wr = weakref.ref(task, lambda _: done.append(None)) del task # while not done: # Truffle change deadline = time.monotonic() + 30.0 while thread._count() != orig and time.monotonic() < deadline: time.sleep(POLL_SLEEP) self.assertEqual(thread._count(), orig)
def test_signals(self): with support.wait_threads_exit(): # Test signal handling semantics of threads. # We spawn a thread, have the thread send two signals, and # wait for it to finish. Check that we got both signals # and that they were run by the main thread. signalled_all.acquire() self.spawnSignallingThread() signalled_all.acquire() # the signals that we asked the kernel to send # will come back, but we don't know when. # (it might even be after the thread exits # and might be out of order.) If we haven't seen # the signals yet, send yet another signal and # wait for it return. if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \ or signal_blackboard[signal.SIGUSR2]['tripped'] == 0: try: signal.alarm(1) signal.pause() finally: signal.alarm(0) self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1) self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'], thread.get_ident()) self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1) self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'], thread.get_ident()) signalled_all.release()
def test_nt_and_posix_stack_size(self): try: thread.stack_size(4096) except ValueError: verbose_print("caught expected ValueError setting " "stack_size(4096)") except thread.error: self.skipTest("platform does not support changing thread stack " "size") fail_msg = "stack_size(%d) failed - should succeed" for tss in (262144, 0x100000, 0): thread.stack_size(tss) self.assertEqual(thread.stack_size(), tss, fail_msg % tss) verbose_print("successfully set stack_size(%d)" % tss) for tss in (262144, 0x100000): verbose_print("trying stack_size = (%d)" % tss) self.next_ident = 0 self.created = 0 with support.wait_threads_exit(): for i in range(NUMTASKS): self.newtask() verbose_print("waiting for all tasks to complete") self.done_mutex.acquire() verbose_print("all tasks done") thread.stack_size(0)
def test_forkinthread(self): non_local = {'status': None} def thread1(): try: pid = os.fork() # fork in a thread except RuntimeError: sys.exit(0) # exit the child if pid == 0: # child os.close(self.read_fd) os.write(self.write_fd, "OK") # Exiting the thread normally in the child process can leave # any additional threads (such as the one started by # importing _tkinter) still running, and this can prevent # the half-zombie child process from being cleaned up. See # Issue #26456. os._exit(0) else: # parent os.close(self.write_fd) pid, status = os.waitpid(pid, 0) non_local['status'] = status with support.wait_threads_exit(): thread.start_new_thread(thread1, ()) self.assertEqual(os.read(self.read_fd, 2), "OK", "Unable to fork() in thread") self.assertEqual(non_local['status'], 0)
def test_forkinthread(self): status = "not set" def thread1(): nonlocal status # fork in a thread pid = os.fork() if pid == 0: # child try: os.close(self.read_fd) os.write(self.write_fd, b"OK") finally: os._exit(0) else: # parent os.close(self.write_fd) pid, status = os.waitpid(pid, 0) with support.wait_threads_exit(): thread.start_new_thread(thread1, ()) self.assertEqual(os.read(self.read_fd, 2), b"OK", "Unable to fork() in thread") self.assertEqual(status, 0)
def test_rlock_acquire_interruption(self): # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck # in a deadlock. # XXX this test can fail when the legacy (non-semaphore) implementation # of locks is used in thread_pthread.h, see issue #11223. oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) try: rlock = thread.RLock() # For reentrant locks, the initial acquisition must be in another # thread. def other_thread(): rlock.acquire() with support.wait_threads_exit(): thread.start_new_thread(other_thread, ()) # Wait until we can't acquire it without blocking... while rlock.acquire(blocking=False): rlock.release() time.sleep(0.01) signal.alarm(1) t1 = time.time() self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5) dt = time.time() - t1 # See rationale above in test_lock_acquire_interruption self.assertLess(dt, 3.0) finally: signal.alarm(0) signal.signal(signal.SIGALRM, oldalrm)
def test_signals(self): with support.wait_threads_exit(): # Test signal handling semantics of threads. # We spawn a thread, have the thread send two signals, and # wait for it to finish. Check that we got both signals # and that they were run by the main thread. signalled_all.acquire() self.spawnSignallingThread() signalled_all.acquire() # the signals that we asked the kernel to send # will come back, but we don't know when. # (it might even be after the thread exits # and might be out of order.) If we haven't seen # the signals yet, send yet another signal and # wait for it return. if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \ or signal_blackboard[signal.SIGUSR2]['tripped'] == 0: try: signal.alarm(1) signal.pause() finally: signal.alarm(0) self.assertEqual(signal_blackboard[signal.SIGUSR1]['tripped'], 1) self.assertEqual(signal_blackboard[signal.SIGUSR1]['tripped_by'], thread.get_ident()) self.assertEqual(signal_blackboard[signal.SIGUSR2]['tripped'], 1) self.assertEqual(signal_blackboard[signal.SIGUSR2]['tripped_by'], thread.get_ident()) signalled_all.release()
def test__count(self): # Test the _count() function. orig = thread._count() mut = thread.allocate_lock() mut.acquire() started = [] def task(): started.append(None) mut.acquire() mut.release() with support.wait_threads_exit(): thread.start_new_thread(task, ()) while not started: time.sleep(POLL_SLEEP) self.assertEqual(thread._count(), orig + 1) # Allow the task to finish. mut.release() # The only reliable way to be sure that the thread ended from the # interpreter's point of view is to wait for the function object to be # destroyed. done = [] wr = weakref.ref(task, lambda _: done.append(None)) del task while not done: time.sleep(POLL_SLEEP) self.assertEqual(thread._count(), orig)
def test_forkinthread(self): pid = None def fork_thread(read_fd, write_fd): nonlocal pid # fork in a thread pid = os.fork() if pid: # parent process return # child process try: os.close(read_fd) os.write(write_fd, b"OK") finally: os._exit(0) with support.wait_threads_exit(): thread.start_new_thread(fork_thread, (self.read_fd, self.write_fd)) self.assertEqual(os.read(self.read_fd, 2), b"OK") os.close(self.write_fd) self.assertIsNotNone(pid) support.wait_process(pid, exitcode=0)
def test_starting_threads(self): with support.wait_threads_exit(): # Basic test for thread creation. for i in range(NUMTASKS): self.newtask() verbose_print("waiting for tasks to complete...") self.done_mutex.acquire() verbose_print("all tasks done")
def test_leak_without_join(self): # bpo-37788: Test that a thread which is not joined explicitly # does not leak. Test written for reference leak checks. def noop(): pass with support.wait_threads_exit(): threading.Thread(target=noop).start()
def test_barrier(self): with support.wait_threads_exit(): self.bar = Barrier(NUMTASKS) self.running = NUMTASKS for i in range(NUMTASKS): thread.start_new_thread(self.task2, (i,)) verbose_print("waiting for tasks to end") self.done_mutex.acquire() verbose_print("tasks done")
def test_ident_of_no_threading_threads(self): # The ident still must work for the main thread and dummy threads. self.assertIsNotNone(threading.currentThread().ident) def f(): ident.append(threading.currentThread().ident) done.set() done = threading.Event() ident = [] with support.wait_threads_exit(): tid = _thread.start_new_thread(f, ()) done.wait() self.assertEqual(ident[0], tid) # Kill the "immortal" _DummyThread del threading._active[ident[0]]
def test_interrupted_timed_acquire(self): # Test to make sure we recompute lock acquisition timeouts when we # receive a signal. Check this by repeatedly interrupting a lock # acquire in the main thread, and make sure that the lock acquire times # out after the right amount of time. # NOTE: this test only behaves as expected if C signals get delivered # to the main thread. Otherwise lock.acquire() itself doesn't get # interrupted and the test trivially succeeds. self.start = None self.end = None self.sigs_recvd = 0 done = thread.allocate_lock() done.acquire() lock = thread.allocate_lock() lock.acquire() def my_handler(signum, frame): self.sigs_recvd += 1 old_handler = signal.signal(signal.SIGUSR1, my_handler) try: def timed_acquire(): self.start = time.time() lock.acquire(timeout=0.5) self.end = time.time() def send_signals(): for _ in range(40): time.sleep(0.02) os.kill(process_pid, signal.SIGUSR1) done.release() with support.wait_threads_exit(): # Send the signals from the non-main thread, since the main thread # is the only one that can process signals. thread.start_new_thread(send_signals, ()) timed_acquire() # Wait for thread to finish done.acquire() # This allows for some timing and scheduling imprecision self.assertLess(self.end - self.start, 2.0) self.assertGreater(self.end - self.start, 0.3) # If the signal is received several times before PyErr_CheckSignals() # is called, the handler will get called less than 40 times. Just # check it's been called at least once. self.assertGreater(self.sigs_recvd, 0) finally: signal.signal(signal.SIGUSR1, old_handler)
def test_unraisable_exception(self): def task(): started.release() raise ValueError("task failed") started = thread.allocate_lock() with support.catch_unraisable_exception() as cm: with support.wait_threads_exit(): started.acquire() thread.start_new_thread(task, ()) started.acquire() self.assertEqual(str(cm.unraisable.exc_value), "task failed") self.assertIs(cm.unraisable.object, task) self.assertEqual(cm.unraisable.err_msg, "Exception ignored in thread started by") self.assertIsNotNone(cm.unraisable.exc_traceback)
def test_interrupted_timed_acquire(self): # Test to make sure we recompute lock acquisition timeouts when we # receive a signal. Check this by repeatedly interrupting a lock # acquire in the main thread, and make sure that the lock acquire times # out after the right amount of time. # NOTE: this test only behaves as expected if C signals get delivered # to the main thread. Otherwise lock.acquire() itself doesn't get # interrupted and the test trivially succeeds. self.start = None self.end = None self.sigs_recvd = 0 done = thread.allocate_lock() done.acquire() lock = thread.allocate_lock() lock.acquire() def my_handler(signum, frame): self.sigs_recvd += 1 old_handler = signal.signal(signal.SIGUSR1, my_handler) try: def timed_acquire(): self.start = time.monotonic() lock.acquire(timeout=0.5) self.end = time.monotonic() def send_signals(): for _ in range(40): time.sleep(0.02) os.kill(process_pid, signal.SIGUSR1) done.release() with support.wait_threads_exit(): # Send the signals from the non-main thread, since the main thread # is the only one that can process signals. thread.start_new_thread(send_signals, ()) timed_acquire() # Wait for thread to finish done.acquire() # This allows for some timing and scheduling imprecision self.assertLess(self.end - self.start, 2.0) self.assertGreater(self.end - self.start, 0.3) # If the signal is received several times before PyErr_CheckSignals() # is called, the handler will get called less than 40 times. Just # check it's been called at least once. self.assertGreater(self.sigs_recvd, 0) finally: signal.signal(signal.SIGUSR1, old_handler)
def test_foreign_thread(self): # Check that a "foreign" thread can use the threading module. def f(mutex): # Calling current_thread() forces an entry for the foreign # thread to get made in the threading._active map. threading.current_thread() mutex.release() mutex = threading.Lock() mutex.acquire() with support.wait_threads_exit(): tid = _thread.start_new_thread(f, (mutex, )) # Wait for the thread to finish. mutex.acquire() self.assertIn(tid, threading._active) self.assertIsInstance(threading._active[tid], threading._DummyThread) #Issue 29376 self.assertTrue(threading._active[tid].is_alive()) self.assertRegex(repr(threading._active[tid]), '_DummyThread') del threading._active[tid]
def test_foreign_thread(self): # Check that a "foreign" thread can use the threading module. def f(mutex): # Calling current_thread() forces an entry for the foreign # thread to get made in the threading._active map. threading.current_thread() mutex.release() mutex = threading.Lock() mutex.acquire() with support.wait_threads_exit(): tid = _thread.start_new_thread(f, (mutex,)) # Wait for the thread to finish. mutex.acquire() self.assertIn(tid, threading._active) self.assertIsInstance(threading._active[tid], threading._DummyThread) #Issue 29376 self.assertTrue(threading._active[tid].is_alive()) self.assertRegex(repr(threading._active[tid]), '_DummyThread') del threading._active[tid]
def test_save_exception_state_on_error(self): # See issue #14474 def task(): started.release() raise SyntaxError def mywrite(self, *args): try: raise ValueError except ValueError: pass real_write(self, *args) started = thread.allocate_lock() with support.captured_output("stderr") as stderr: real_write = stderr.write stderr.write = mywrite started.acquire() with support.wait_threads_exit(): thread.start_new_thread(task, ()) started.acquire() self.assertIn("Traceback", stderr.getvalue())
def test_save_exception_state_on_error(self): # See issue #14474 def task(): started.release() raise SyntaxError def mywrite(self, *args): try: raise ValueError except ValueError: pass real_write(self, *args) c = thread._count() started = thread.allocate_lock() with support.captured_output("stderr") as stderr: real_write = stderr.write stderr.write = mywrite started.acquire() with support.wait_threads_exit(): thread.start_new_thread(task, ()) started.acquire() self.assertIn("Traceback", stderr.getvalue())
def test_reacquire(self): # Lock needs to be released before re-acquiring. lock = self.locktype() phase = [] def f(): lock.acquire() phase.append(None) lock.acquire() phase.append(None) with support.wait_threads_exit(): start_new_thread(f, ()) while len(phase) == 0: _wait() _wait() self.assertEqual(len(phase), 1) lock.release() while len(phase) == 1: _wait() self.assertEqual(len(phase), 2)