<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Tue, Jun 13, 2017 at 12:10 AM, Nick Coghlan <span dir="ltr"><<a href="mailto:ncoghlan@gmail.com" target="_blank">ncoghlan@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><br>

reporting failures from concurrent.futures.wait:<br>
<a href="https://pythonhosted.org/futures/#concurrent.futures.wait" rel="noreferrer" target="_blank">https://pythonhosted.org/<wbr>futures/#concurrent.futures.<wbr>wait</a></blockquote><div><br></div><div>Yeah, and asyncio.gather is another example in the stdlib. Or there's twisted's DeferredList. Trio is unusual in effectively forcing all tasks to be run under a gather(), but the basic concept isn't unique at all.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Figuring out how to *display* an exception tree coherently is going to<br>
be a pain (it's already problematic with just the linked list), but if<br>
we can at least model exception trees consistently, then we'd be able<br>
to share that display logic, even if the scenarios resulting in<br>
MultiErrors varied.<br></blockquote><div><br></div><div>It's true, it was a pain :-). And I'm sure it can be refined. But I think this is a reasonable first cut: <a href="https://github.com/python-trio/trio/blob/9e0df6159e55fe5e389ae5e24f9bbe51e9b77943/trio/_core/_multierror.py#L341-L388">https://github.com/python-trio/trio/blob/9e0df6159e55fe5e389ae5e24f9bbe51e9b77943/trio/_core/_multierror.py#L341-L388</a><br></div><br></div><div class="gmail_quote">Basically the algorithm there is:<br><br></div><div class="gmail_quote">if there's a __cause__ or __context__:<br></div><div class="gmail_quote">  print it (recursively using this algorithm)<br></div><div class="gmail_quote">  print the description line ("this exception was the direct cause" or whatever is appropriate)<br></div><div class="gmail_quote">print the traceback and str() attached to this exception itself<br></div><div class="gmail_quote">for each embedded exception:<br></div><div class="gmail_quote">  print "Details of embedded exception  {i}:"<br></div><div class="gmail_quote">  with extra indentation:<br>     print the embedded exception (recursively using this algorithm)<br></div><div class="gmail_quote">(+ some memoization to avoid infinite recursion on loopy structures)<br></div><div class="gmail_quote"><br></div><div class="gmail_extra">Of course really complicated trainwrecks that send exception shrapnel flying everywhere can still be complicated to read, but ... that's why we make the big bucks, I guess. (My fingers initially typoed that as "that's why we make the big bugs". Just throwing that out there.) <br></div><div class="gmail_quote"><br></div>Currently that code has a hard-coded assumption that the only kind of exception-container is MultiError, but I guess it wouldn't be too hard to extend that into some kind of protocol that your  asymmetric CleanupError could also participate in? Like: an abstract exception container has 0 or 1 (predecessor exception, description) pairs [i.e., __cause__ or __context__], plus 0 or more (embedded exception, description) pairs, and then the printing code just walks the resulting tree using the above algorithm?<br><br></div><div class="gmail_extra">If this all works out at the library level, then one can start to imagine ways that the interpreter could potentially get benefits from participating:<br><br></div><div class="gmail_extra">- right now, if an exception that already has a __cause__ or __context__ is re-raised, then implicit exception chaining will  overwrite  the old __cause__ or __context__. Instead, it could check to see if there's already a non-None value there, and if so do exc.__context__ = MultiError([exc.__context__, new_exc]).<br><br></div><div class="gmail_extra">- standardizing the protocol for walking over an exception container would let us avoid fighting over who owns sys.excepthook, and make it easier for third-party exception printers (like IPython, pytest, etc.) to play along.<br><br></div><div class="gmail_extra">- MultiError.catch is a bit awkward. If we were going all-in on this then one can imagine some construct like<br><br></div><div class="gmail_extra">multitry:<br></div><div class="gmail_extra">    ... do stuff ...<br></div><div class="gmail_extra">    raise MultiError([ValueError(1), MultiError([TypeError(), ValueError(2)])])<br></div><div class="gmail_extra">multiexcept ValueError as exc:<br></div><div class="gmail_extra">    print("caught a ValueError", exc)<br></div><div class="gmail_extra">multiexcept TypeError:<br></div><div class="gmail_extra">    print("caught a TypeError and raising RuntimeError")<br></div><div class="gmail_extra">    raise RuntimeError<br><br></div><div class="gmail_extra">which prints<br><br></div><div class="gmail_extra">  caught a ValueError 1<br></div><div class="gmail_extra">  caught a TypeError and raising RuntimeError<br></div><div class="gmail_extra">  caught a ValueError 2<br></div><div class="gmail_extra"><br></div><div class="gmail_extra">and then raises a RuntimeError with its __context__ pointing to the TypeError.<br></div><br><div class="gmail_extra">...but of course that's preeeeetty speculative at this point; definitely more python-ideas territory.<br></div><div class="gmail_extra"><br></div><div class="gmail_extra">-n<br clear="all"></div><div class="gmail_extra"><br>-- <br><div class="gmail_signature"><div dir="ltr"><div>Nathaniel J. Smith -- <a href="http://vorpus.org" target="_blank">https://vorpus.org</a></div></div></div>
</div></div>