Skip to content

Commit

Permalink
Merge pull request #631 from belm0/run_impl_frames
Browse files Browse the repository at this point in the history
Elide task.context.run() and contextvars.callable() frames from tracebacks
  • Loading branch information
njsmith authored Aug 28, 2018
2 parents 1095e37 + c9087e9 commit fc7c7e0
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 1 deletion.
9 changes: 8 additions & 1 deletion trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import random
import select
import sys
import threading
from collections import deque
import collections.abc
Expand Down Expand Up @@ -1391,7 +1392,13 @@ def run_impl(runner, async_fn, args):
except StopIteration as stop_iteration:
final_result = Value(stop_iteration.value)
except BaseException as task_exc:
final_result = Error(task_exc)
# Store for later, removing uninteresting top frames:
# 1. trio._core._run.run_impl()
# 2. contextvars.Context.run() (< Python 3.7 only)
tb_next = task_exc.__traceback__.tb_next
if sys.version_info < (3, 7):
tb_next = tb_next.tb_next
final_result = Error(task_exc.with_traceback(tb_next))

if final_result is not None:
# We can't call this directly inside the except: blocks above,
Expand Down
30 changes: 30 additions & 0 deletions trio/_core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import threading
import time
import traceback
import warnings
from contextlib import contextmanager
from math import inf
Expand Down Expand Up @@ -1897,6 +1898,35 @@ def handle(exc):
assert result == [[0, 0], [1, 1]]


async def test_traceback_frame_removal():
async def my_child_task():
raise KeyError()

try:
# Trick: For now cancel/nursery scopes still leave a bunch of tb gunk
# behind. But if there's a MultiError, they leave it on the MultiError,
# which lets us get a clean look at the KeyError itself. Someday I
# guess this will always be a MultiError (#611), but for now we can
# force it by raising two exceptions.
async with _core.open_nursery() as nursery:
nursery.start_soon(my_child_task)
nursery.start_soon(my_child_task)
except _core.MultiError as exc:
first_exc = exc.exceptions[0]
assert isinstance(first_exc, KeyError)
# The top frame in the exception traceback should be inside the child
# task, not trio/contextvars internals. And there's only one frame
# inside the child task, so this will also detect if our frame-removal
# is too eager.
#frame = first_exc.__traceback__.tb_frame
#assert frame.f_code is my_child_task.__code__
# ...but we're not there yet. There are several frames from nursery's
# __aexit__, starting with _nested_child_finished().
frames = traceback.extract_tb(first_exc.__traceback__)
functions = [function for _, _, function, _ in frames]
assert functions[-2:] == ['_nested_child_finished', 'my_child_task']


def test_contextvar_support():
var = contextvars.ContextVar("test")
var.set("before")
Expand Down

0 comments on commit fc7c7e0

Please sign in to comment.