Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Interoperating with Trio #1210

Open
njsmith opened this issue Mar 16, 2018 · 4 comments
Open

Interoperating with Trio #1210

njsmith opened this issue Mar 16, 2018 · 4 comments
Assignees

Comments

@njsmith
Copy link

njsmith commented Mar 16, 2018

Hello! I'm a maintainer of Trio, an async library for Python. (Think of it as an alternative to asyncio.) We just had someone try to use it and raven together, and they ran into some issues: python-trio/trio#469

I'm not sure what the best way to fix these is, and it might require changes on both sides, so I figured it was worth starting a conversation. There are a few different things.

Raven crashes when trying to process Trio program stack frames

Trio, for extremely annoying reasons related to limitations in the Python interpreter and control-C handling, has to store some metadata inside magic invisible local variables that it injects into some user frames [1]. In order to make sure that we don't collide with actual user local variables, we do this by storing an entry in the frame.f_locals dict with a unique sentinel object as its key, i.e., it's a local variable but its name is not a string. Yeah, Python allows this – who knew, right? Well... apparently raven didn't :-). It makes raven crash. On the one hand, trio's behavior here is The Right Thing, and raven might want to be robust against this in general, given that Python does allow it. OTOH, if raven is breaking then probably other things will also break, and maybe I should just hold my nose and name the variable __trio_internal_value_please_ignore_this_and_dont_name_any_variables_like_this_awoiehiuawf or whatever.

Trio's extensions to Python's extension semantics cause problems with/for Raven

This is more interesting. Trio is a concurrency library, and one of the fun advantages of concurrent programs over sequential programs is that in a concurrency library, you can have multiple things crash at the same time. Most (all other?) concurrency libraries for Python handle this by throwing some exceptions on the floor and hoping for the best. (I guess you of all projects are aware of this!) Trio doesn't throw exceptions on the floor; instead it arranges its tasks in a tree, and if one task doesn't handle an exception it flows up into its parent task, etc., until it eventually hits the root of the whole program. But this means that you have to handle the case where two exceptions are propagating independently and bump into each other. Python... doesn't really have any way to handle this, but we refuse to throw exceptions on the floor, so we're stuck hacking around Python's limitations.

Basically the way we do this is with a MultiError class, which is an Exception class that acts as a container for other exceptions. It's actually fairly sophisticated: you can have MultiErrors nested inside each other, and the nested structure represents the nested structure of the task tree, we have mechanisms to catch "part of" a MultiError and trio knows how to fix up tracebacks afterwards, we can print tracebacks in a nice way where we first show the path that each exception took independently, then as they join up we switch to printing the common traceback together, etc.

There's a simple example here to give you the idea.

Of course, raven has no idea about any of this.

One issue this causes is that trio and raven both want to take over sys.excepthook. They're both written carefully, but since trio actually wants to replace the default handling of tracebacks, it can't do raven's trick of falling back on the regular hook; instead, if it detects that some other library has been missing with sys.excepthook, then it prints a warning and skips installing its hook. End result: if you import raven, trio, then exception printing is broken. import trio, raven OTOH works fine. It'd be nice if we could coordinate to make this work both ways. Any ideas?

The other deeper issue is obviously raven has no idea about these special MultiError objects, so even after we get excepthook to run, it won't actually be able to report proper traceback information. Ideally raven should be peeking inside to gather up the full traceback tree, especially since the topmost MultiError's traceback will usually be the boring stuff you already knew like "oh the error happened underneath main that's nice good to know", and it's the embedded exceptions that have the information on the frames where the errors actually occurred.

I'd love to hear any thoughts here.


[1] If you really want to know the details, there's an exhaustive/exhausting blog post here, and bpo-12857 is the upstream issue that I'm hoping we might be able to fix for 3.8. But in the mean time we're stuck with this.

@mitsuhiko
Copy link
Contributor

Just a quick comment about storing integers in f_locals: is not a great idea because it requires that the dict representation allows non string keys which is normally not the case.

@njsmith
Copy link
Author

njsmith commented Mar 17, 2018

@mitsuhiko You mean, you lose the optimization for dicts where all the keys are strings? I guess that's true, but I don't think it matters – the f_locals dict isn't used at all during normal execution.

@miracle2k
Copy link

I ran into the LOCALS_KEY_KI_PROTECTION_ENABLED :/ I thought maybe we can adapt the JSON encoder to deal with this, but it doesn't seem possible to change it's behaviour with any default types (in this case, a dict).

@brakhane
Copy link

I have prepared #1273 that fixes that problem, but it still hasn't been merged yet.

FWIW, python-trio/trio#599 which is included in 0.7.0 also prevents this issue from occurring.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants