test_threading.py 15.5 KB
Newer Older
1 2
# Very rudimentary test of threading module

3 4
import test.support
from test.support import verbose
5
import random
Georg Brandl's avatar
Georg Brandl committed
6
import re
7
import sys
8
import threading
9
import _thread
10
import time
11
import unittest
12
import weakref
13

14 15 16 17 18 19 20 21 22 23
# A trivial mutable counter.
class Counter(object):
    def __init__(self):
        self.value = 0
    def inc(self):
        self.value += 1
    def dec(self):
        self.value -= 1
    def get(self):
        return self.value
24 25

class TestThread(threading.Thread):
26 27 28 29 30 31 32
    def __init__(self, name, testcase, sema, mutex, nrunning):
        threading.Thread.__init__(self, name=name)
        self.testcase = testcase
        self.sema = sema
        self.mutex = mutex
        self.nrunning = nrunning

33
    def run(self):
Christian Heimes's avatar
Christian Heimes committed
34
        delay = random.random() / 10000.0
35
        if verbose:
36
            print('task %s will run for %.1f usec' %
37
                  (self.get_name(), delay * 1e6))
38

Christian Heimes's avatar
Christian Heimes committed
39 40 41 42 43 44
        with self.sema:
            with self.mutex:
                self.nrunning.inc()
                if verbose:
                    print(self.nrunning.get(), 'tasks are running')
                self.testcase.assert_(self.nrunning.get() <= 3)
45

Christian Heimes's avatar
Christian Heimes committed
46 47
            time.sleep(delay)
            if verbose:
48 49
                print('task', self.get_name(), 'done')

Christian Heimes's avatar
Christian Heimes committed
50 51 52 53 54
            with self.mutex:
                self.nrunning.dec()
                self.testcase.assert_(self.nrunning.get() >= 0)
                if verbose:
                    print('%s is finished. %d tasks are running' %
55 56
                          (self.get_name(), self.nrunning.get()))

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

class ThreadTests(unittest.TestCase):

    # Create a bunch of threads, let each do some work, wait until all are
    # done.
    def test_various_ops(self):
        # This takes about n/3 seconds to run (about n/3 clumps of tasks,
        # times about 1 second per clump).
        NUMTASKS = 10

        # no more than 3 of the 10 can run at once
        sema = threading.BoundedSemaphore(value=3)
        mutex = threading.RLock()
        numrunning = Counter()

        threads = []

        for i in range(NUMTASKS):
            t = TestThread("<thread %d>"%i, self, sema, mutex, numrunning)
            threads.append(t)
77
            self.failUnlessEqual(t.get_ident(), None)
Georg Brandl's avatar
Georg Brandl committed
78
            self.assert_(re.match('<TestThread\(.*, initial\)>', repr(t)))
79 80 81
            t.start()

        if verbose:
82
            print('waiting for all tasks to complete')
83
        for t in threads:
84
            t.join(NUMTASKS)
85 86
            self.assert_(not t.is_alive())
            self.failIfEqual(t.get_ident(), 0)
Georg Brandl's avatar
Georg Brandl committed
87
            self.assert_(re.match('<TestThread\(.*, \w+ -?\d+\)>', repr(t)))
88
        if verbose:
89
            print('all tasks done')
90 91
        self.assertEqual(numrunning.get(), 0)

92 93 94
    # run with a small(ish) thread stack size (256kB)
    def test_various_ops_small_stack(self):
        if verbose:
95
            print('with 256kB thread stack size...')
96 97
        try:
            threading.stack_size(262144)
98
        except _thread.error:
99
            if verbose:
100
                print('platform does not support changing thread stack size')
101 102 103 104 105 106 107
            return
        self.test_various_ops()
        threading.stack_size(0)

    # run with a large thread stack size (1MB)
    def test_various_ops_large_stack(self):
        if verbose:
108
            print('with 1MB thread stack size...')
109 110
        try:
            threading.stack_size(0x100000)
111
        except _thread.error:
112
            if verbose:
113
                print('platform does not support changing thread stack size')
114 115 116 117
            return
        self.test_various_ops()
        threading.stack_size(0)

118 119 120 121 122 123 124 125 126 127 128 129
    def test_foreign_thread(self):
        # Check that a "foreign" thread can use the threading module.
        def f(mutex):
            # Acquiring an RLock forces an entry for the foreign
            # thread to get made in the threading._active map.
            r = threading.RLock()
            r.acquire()
            r.release()
            mutex.release()

        mutex = threading.Lock()
        mutex.acquire()
130
        tid = _thread.start_new_thread(f, (mutex,))
131 132 133 134 135 136
        # Wait for the thread to finish.
        mutex.acquire()
        self.assert_(tid in threading._active)
        self.assert_(isinstance(threading._active[tid],
                                threading._DummyThread))
        del threading._active[tid]
137

138 139 140 141 142 143 144
    # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently)
    # exposed at the Python level.  This test relies on ctypes to get at it.
    def test_PyThreadState_SetAsyncExc(self):
        try:
            import ctypes
        except ImportError:
            if verbose:
145
                print("test_PyThreadState_SetAsyncExc can't import ctypes")
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
            return  # can't do anything

        set_async_exc = ctypes.pythonapi.PyThreadState_SetAsyncExc

        class AsyncExc(Exception):
            pass

        exception = ctypes.py_object(AsyncExc)

        # `worker_started` is set by the thread when it's inside a try/except
        # block waiting to catch the asynchronously set AsyncExc exception.
        # `worker_saw_exception` is set by the thread upon catching that
        # exception.
        worker_started = threading.Event()
        worker_saw_exception = threading.Event()

        class Worker(threading.Thread):
            def run(self):
164
                self.id = _thread.get_ident()
165 166 167 168 169 170 171 172 173 174 175
                self.finished = False

                try:
                    while True:
                        worker_started.set()
                        time.sleep(0.1)
                except AsyncExc:
                    self.finished = True
                    worker_saw_exception.set()

        t = Worker()
176
        t.set_daemon(True) # so if this fails, we don't hang Python at shutdown
177 178
        t.start()
        if verbose:
179
            print("    started worker thread")
180 181 182

        # Try a thread id that doesn't make sense.
        if verbose:
183
            print("    trying nonsensical thread id")
184 185 186 187 188
        result = set_async_exc(ctypes.c_long(-1), exception)
        self.assertEqual(result, 0)  # no thread states modified

        # Now raise an exception in the worker thread.
        if verbose:
189
            print("    waiting for worker thread to get started")
190 191
        worker_started.wait()
        if verbose:
192
            print("    verifying worker hasn't exited")
193 194
        self.assert_(not t.finished)
        if verbose:
195
            print("    attempting to raise asynch exception in worker")
196 197 198
        result = set_async_exc(ctypes.c_long(t.id), exception)
        self.assertEqual(result, 1) # one thread state modified
        if verbose:
199
            print("    waiting for worker to say it caught the exception")
200 201 202
        worker_saw_exception.wait(timeout=10)
        self.assert_(t.finished)
        if verbose:
203
            print("    all OK -- joining worker")
204 205 206 207
        if t.finished:
            t.join()
        # else the thread is still running, and we have no way to kill it

208 209 210 211 212 213 214 215 216 217 218 219 220
    def test_finalize_runnning_thread(self):
        # Issue 1402: the PyGILState_Ensure / _Release functions may be called
        # very late on python exit: on deallocation of a running thread for
        # example.
        try:
            import ctypes
        except ImportError:
            if verbose:
                print("test_finalize_with_runnning_thread can't import ctypes")
            return  # can't do anything

        import subprocess
        rc = subprocess.call([sys.executable, "-c", """if 1:
221
            import ctypes, sys, time, _thread
222

Christian Heimes's avatar
Christian Heimes committed
223
            # This lock is used as a simple event variable.
224
            ready = _thread.allocate_lock()
Christian Heimes's avatar
Christian Heimes committed
225 226
            ready.acquire()

227 228 229 230 231 232 233 234 235 236 237
            # Module globals are cleared before __del__ is run
            # So we save the functions in class dict
            class C:
                ensure = ctypes.pythonapi.PyGILState_Ensure
                release = ctypes.pythonapi.PyGILState_Release
                def __del__(self):
                    state = self.ensure()
                    self.release(state)

            def waitingThread():
                x = C()
Christian Heimes's avatar
Christian Heimes committed
238
                ready.release()
239 240
                time.sleep(100)

241
            _thread.start_new_thread(waitingThread, ())
Christian Heimes's avatar
Christian Heimes committed
242
            ready.acquire()  # Be sure the other thread is waiting.
243 244 245 246
            sys.exit(42)
            """])
        self.assertEqual(rc, 42)

Neal Norwitz's avatar
Neal Norwitz committed
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
    def test_finalize_with_trace(self):
        # Issue1733757
        # Avoid a deadlock when sys.settrace steps into threading._shutdown
        import subprocess
        rc = subprocess.call([sys.executable, "-c", """if 1:
            import sys, threading

            # A deadlock-killer, to prevent the
            # testsuite to hang forever
            def killer():
                import os, time
                time.sleep(2)
                print('program blocked; aborting')
                os._exit(2)
            t = threading.Thread(target=killer)
262
            t.set_daemon(True)
Neal Norwitz's avatar
Neal Norwitz committed
263 264 265 266
            t.start()

            # This is the trace function
            def func(frame, event, arg):
267
                threading.current_thread()
Neal Norwitz's avatar
Neal Norwitz committed
268 269 270 271 272 273 274 275
                return func

            sys.settrace(func)
            """])
        self.failIf(rc == 2, "interpreted was blocked")
        self.failUnless(rc == 0, "Unexpected error")


276 277 278 279 280 281
    def test_enumerate_after_join(self):
        # Try hard to trigger #1703448: a thread is still returned in
        # threading.enumerate() after it has been join()ed.
        enum = threading.enumerate
        old_interval = sys.getcheckinterval()
        try:
282 283 284 285
            for i in range(1, 100):
                # Try a couple times at each thread-switching interval
                # to get more interleavings.
                sys.setcheckinterval(i // 5)
286 287 288 289 290 291 292 293 294
                t = threading.Thread(target=lambda: None)
                t.start()
                t.join()
                l = enum()
                self.assertFalse(t in l,
                    "#1703448 triggered after %d trials: %s" % (i, l))
        finally:
            sys.setcheckinterval(old_interval)

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    def test_no_refcycle_through_target(self):
        class RunSelfFunction(object):
            def __init__(self, should_raise):
                # The links in this refcycle from Thread back to self
                # should be cleaned up when the thread completes.
                self.should_raise = should_raise
                self.thread = threading.Thread(target=self._run,
                                               args=(self,),
                                               kwargs={'yet_another':self})
                self.thread.start()

            def _run(self, other_ref, yet_another):
                if self.should_raise:
                    raise SystemExit

        cyclic_object = RunSelfFunction(should_raise=False)
        weak_cyclic_object = weakref.ref(cyclic_object)
        cyclic_object.thread.join()
        del cyclic_object
314 315 316
        self.assertEquals(None, weak_cyclic_object(),
                          msg=('%d references still around' %
                               sys.getrefcount(weak_cyclic_object())))
317 318 319 320 321

        raising_cyclic_object = RunSelfFunction(should_raise=True)
        weak_raising_cyclic_object = weakref.ref(raising_cyclic_object)
        raising_cyclic_object.thread.join()
        del raising_cyclic_object
322 323 324
        self.assertEquals(None, weak_raising_cyclic_object(),
                          msg=('%d references still around' %
                               sys.getrefcount(weak_raising_cyclic_object())))
325

326

Jesse Noller's avatar
Jesse Noller committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
class ThreadJoinOnShutdown(unittest.TestCase):

    def _run_and_join(self, script):
        script = """if 1:
            import sys, os, time, threading

            # a thread, which waits for the main program to terminate
            def joiningfunc(mainthread):
                mainthread.join()
                print('end of thread')
        \n""" + script

        import subprocess
        p = subprocess.Popen([sys.executable, "-c", script], stdout=subprocess.PIPE)
        rc = p.wait()
342 343
        data = p.stdout.read().decode().replace('\r', '')
        self.assertEqual(data, "end of main\nend of thread\n")
Jesse Noller's avatar
Jesse Noller committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
        self.failIf(rc == 2, "interpreter was blocked")
        self.failUnless(rc == 0, "Unexpected error")

    def test_1_join_on_shutdown(self):
        # The usual case: on exit, wait for a non-daemon thread
        script = """if 1:
            import os
            t = threading.Thread(target=joiningfunc,
                                 args=(threading.current_thread(),))
            t.start()
            time.sleep(0.1)
            print('end of main')
            """
        self._run_and_join(script)


    def test_2_join_in_forked_process(self):
        # Like the test above, but from a forked interpreter
        import os
        if not hasattr(os, 'fork'):
            return
        script = """if 1:
            childpid = os.fork()
            if childpid != 0:
                os.waitpid(childpid, 0)
                sys.exit(0)

            t = threading.Thread(target=joiningfunc,
                                 args=(threading.current_thread(),))
            t.start()
            print('end of main')
            """
        self._run_and_join(script)

378 379
    # XXX This test hangs!
    def Xtest_3_join_in_forked_from_thread(self):
Jesse Noller's avatar
Jesse Noller committed
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
        # Like the test above, but fork() was called from a worker thread
        # In the forked process, the main Thread object must be marked as stopped.
        import os
        if not hasattr(os, 'fork'):
            return
        script = """if 1:
            main_thread = threading.current_thread()
            def worker():
                childpid = os.fork()
                if childpid != 0:
                    os.waitpid(childpid, 0)
                    sys.exit(0)

                t = threading.Thread(target=joiningfunc,
                                     args=(main_thread,))
                print('end of main')
                t.start()
                t.join() # Should not block: main_thread is already stopped

            w = threading.Thread(target=worker)
            w.start()
            """
        self._run_and_join(script)


405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
class ThreadingExceptionTests(unittest.TestCase):
    # A RuntimeError should be raised if Thread.start() is called
    # multiple times.
    def test_start_thread_again(self):
        thread = threading.Thread()
        thread.start()
        self.assertRaises(RuntimeError, thread.start)

    def test_releasing_unacquired_rlock(self):
        rlock = threading.RLock()
        self.assertRaises(RuntimeError, rlock.release)

    def test_waiting_on_unacquired_condition(self):
        cond = threading.Condition()
        self.assertRaises(RuntimeError, cond.wait)

    def test_notify_on_unacquired_condition(self):
        cond = threading.Condition()
        self.assertRaises(RuntimeError, cond.notify)

    def test_semaphore_with_negative_value(self):
        self.assertRaises(ValueError, threading.Semaphore, value = -1)
427
        self.assertRaises(ValueError, threading.Semaphore, value = -sys.maxsize)
428 429

    def test_joining_current_thread(self):
430 431
        current_thread = threading.current_thread()
        self.assertRaises(RuntimeError, current_thread.join);
432 433 434 435 436 437 438 439

    def test_joining_inactive_thread(self):
        thread = threading.Thread()
        self.assertRaises(RuntimeError, thread.join)

    def test_daemonize_active_thread(self):
        thread = threading.Thread()
        thread.start()
440
        self.assertRaises(RuntimeError, thread.set_daemon, True)
441 442


443
def test_main():
444
    test.support.run_unittest(ThreadTests,
Jesse Noller's avatar
Jesse Noller committed
445 446 447
                                   ThreadJoinOnShutdown,
                                   ThreadingExceptionTests,
                                   )
448 449 450

if __name__ == "__main__":
    test_main()