Skip to content

Stack info lost when handling exception from thread #2073

Description

@mickp

IronPython appears to lose frames from within the thread, where they are retained in CPython

(May be duplicate of #738 , or perhaps just related, but it seemed worth submitting the tests.)

Minimal repro.

import functools
import sys
import threading
import traceback
import unittest

print(sys.version)

class AsyncTask(object):
    def __init__(self, func):
        functools.wraps(func)(self)
        self._func = func
        self._result = None
        self._exception = None

    def __call__(self, *args, **kwargs):
        self._thread = threading.Thread(target=self._run, args=args, kwargs=kwargs)
        self._thread.start()
        return self

    def _run(self, *args, **kwargs):
        try:
            self._result = self._func(*args, **kwargs)
        except Exception as err:
            self._exception = err
        finally:
            self._complete = True

    @property
    def result(self):
        if self._thread is None:
            raise RuntimeError("Not started.")
        self._thread.join()
        if self._exception is not None:
            raise self._exception
        return self._result

    def await_task(self, timeout=None):
        if self._thread is None:
            raise RuntimeError("Not started.")
        self._thread.join(timeout)
        completed = not self._thread.is_alive()
        if completed and self._exception is not None:
            raise self._exception
        return completed


def module_level_raising_function(exc):
    raise exc


class TestAsyncTaskException(unittest.TestCase):
    def test_with_function_scoped_def(self):
        def raising_function(exc):
            raise exc

        return self._test(raising_function)

    def test_with_module_scoped_def(self):
        self._test(module_level_raising_function)

    def _test(self, raising_func):
        the_exception = Exception('the exception message')
        task = AsyncTask(raising_func)(the_exception)
        caught_exception = None
        try:
            task.result
        except Exception as caught_exception:
            etype, evalue, etb = sys.exc_info()
            trace = traceback.extract_tb(etb)
            self.assertIs(caught_exception, the_exception)

            print("\nTraceback for {}:\n{}".format(self._testMethodName, '\n'.join(traceback.format_tb(etb))))

            (filenames, line_numbers, function_names, texts) = zip(*trace)
            self.assertIn(raising_func.__name__, function_names)
        else:
            self.fail("Exception not raised.")


if __name__ == '__main__':
    unittest.main()

Expected result - CPython 3.4

3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:24:06) [MSC v.1600 32 bit (Intel)]

Traceback for test_with_function_scoped_def:
  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 68, in _test
    task.result

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 36, in result
    raise self._exception

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 24, in _run
    self._result = self._func(*args, **kwargs)

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 56, in raising_function
    raise exc

.
Traceback for test_with_module_scoped_def:
  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 68, in _test
    task.result

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 36, in result
    raise self._exception

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 24, in _run
    self._result = self._func(*args, **kwargs)

  File "C:\Users\michael.phillips\repos\AsyncTask\minrepro.py", line 50, in module_level_raising_function
    raise exc

.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Observed result - IronPython 3.4

[.NETFramework,Version=v4.6.2 on .NET Framework 4.8.9324.0 (64-bit)]

Traceback for test_with_function_scoped_def:
  File ".\minrepro.py", line 68, in _test
    task.result

  File ".\minrepro.py", line 36, in result
    raise self._exception

F
Traceback for test_with_module_scoped_def:
  File ".\minrepro.py", line 68, in _test
    task.result

  File ".\minrepro.py", line 36, in result
    raise self._exception

F
======================================================================
FAIL: test_with_function_scoped_def (minrepro.TestAsyncTaskException)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ".\minrepro.py", line 58, in test_with_function_scoped_def
    return self._test(raising_function)
  File ".\minrepro.py", line 77, in _test
    self.assertIn(raising_func.__name__, function_names)
AssertionError: 'raising_function' not found in ('_test', 'result')

======================================================================
FAIL: test_with_module_scoped_def (minrepro.TestAsyncTaskException)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ".\minrepro.py", line 61, in test_with_module_scoped_def
    self._test(module_level_raising_function)
  File ".\minrepro.py", line 77, in _test
    self.assertIn(raising_func.__name__, function_names)
AssertionError: 'module_level_raising_function' not found in ('_test', 'result')

----------------------------------------------------------------------
Ran 2 tests in 0.620s

FAILED (failures=2)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions