IronLanguages / IronLanguages/ironpython3
Stack info lost when handling exception from thread
Open
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 2.8k
- Forks
- 316
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 1
Description
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)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the minimal repro in minrepro.py under IronPython and CPython, focusing on AsyncTask._run and the result property where the exception crosses the thread boundary. Done means the IronPython traceback retains the raising_function or module_level_raising_function frame, matching the expected CPython output and passing both unittest cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100