Tribler/py-ipv8

Infinite loop in `TestBase` if event loop is stopped with non-daemon thread

qstokkink opened this issue · 0 comments

The following test case spawns a Thread and stops the event loop. The unit test test_crash_evl is expected to be terminated after 1 second (MAX_TEST_TIME). However, the following code never terminates:

from asyncio import get_event_loop
from threading import Thread

from .base import TestBase


class TestCrash(TestBase):

    MAX_TEST_TIME = 1.0

    def inf_loop(self):
        while True:
            pass

    def setUp(self):
        super().setUp()
        self.thread = Thread(target=self.inf_loop)
        self.thread.start()

    def tearDown(self):
        super().tearDown()
        self.thread.join()

    def test_crash_evl(self):
        get_event_loop().stop()

The log output for this case is as follows:

The test-suite locked up! Force quitting! Thread dump:
THREAD#139684644501248
|   File "/usr/lib/python3.8/threading.py", line 890, in _bootstrap
|       self._bootstrap_inner()
|   File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
|       self.run()
|   File "/usr/lib/python3.8/threading.py", line 870, in run
|       self._target(*self._args, **self._kwargs)
|   File "/py-ipv8/ipv8/test/test_crash.py", line 13, in inf_loop
|       pass
THREAD#139684679137088
|   File "/snap/pycharm-community/336/plugins/python-ce/helpers/pycharm/_jb_nosetest_runner.py", line 19, in <module>
|       sys.exit(nose.main(addplugins=[TeamcityReport()]))
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/core.py", line 118, in __init__
|       unittest.TestProgram.__init__(
|   File "/usr/lib/python3.8/unittest/main.py", line 101, in __init__
|       self.runTests()
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/core.py", line 207, in runTests
|       result = self.testRunner.run(self.test)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/core.py", line 62, in run
|       test(result)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/suite.py", line 178, in __call__
|       return self.run(*arg, **kw)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/suite.py", line 225, in run
|       test(orig)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/suite.py", line 178, in __call__
|       return self.run(*arg, **kw)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/suite.py", line 225, in run
|       test(orig)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/case.py", line 46, in __call__
|       return self.run(*arg, **kwarg)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/case.py", line 134, in run
|       self.runTest(result)
|   File "/home/quinten/.local/lib/python3.8/site-packages/nose/case.py", line 152, in runTest
|       test(result)
|   File "/usr/lib/python3.8/unittest/case.py", line 736, in __call__
|       return self.run(*args, **kwds)
|   File "/usr/lib/python3.8/unittest/async_case.py", line 158, in run
|       return super().run(result)
|   File "/usr/lib/python3.8/unittest/case.py", line 679, in run
|       self._callTearDown()
|   File "/py-ipv8/ipv8/test/base.py", line 174, in _callTearDown
|       self.__call_internal_in_context(self.tearDown)
|   File "/py-ipv8/ipv8/test/base.py", line 161, in __call_internal_in_context
|       self._callTestMethod(func)
|   File "/usr/lib/python3.8/unittest/async_case.py", line 65, in _callTestMethod
|       self._callMaybeAsync(method)
|   File "/usr/lib/python3.8/unittest/async_case.py", line 84, in _callMaybeAsync
|       ret = func(*args, **kwargs)
|   File "/py-ipv8/ipv8/test/test_crash.py", line 22, in tearDown
|       self.thread.join()
|   File "/usr/lib/python3.8/threading.py", line 1011, in join
|       self._wait_for_tstate_lock()
|   File "/usr/lib/python3.8/threading.py", line 1027, in _wait_for_tstate_lock
|       elif lock.acquire(block, timeout):
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/py-ipv8/ipv8/test/base.py", line 272, in check_loop
    tasks = all_tasks(get_event_loop())
  File "/usr/lib/python3.8/asyncio/events.py", line 639, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-1'.

In this case, the call to get_event_loop() crashes in the lock-up detection of TestBase:

py-ipv8/ipv8/test/base.py

Lines 272 to 276 in 8d2d09c

tasks = all_tasks(get_event_loop())
if tasks:
print("Pending tasks:") # noqa: T001
for task in tasks:
print("> %s" % task) # noqa: T001

To fix this issue, this code should handle the case of an event loop being stopped or closed (and perhaps - in exotic situations - not existing at all).