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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions