<div dir="ltr">First off, +1 on propagating the CancelledError failure (or something even more specific) all the way back up the errback chain.<div><br></div><div style>lvh> <span style="font-family:arial,sans-serif;font-size:13px">Personally, I think it's enough of a change in functionality to warrant a chance in ways a function can fail</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">I'm not sure what change in functionality you mean. Deferreds are already cancelable, and functions aren't going to fail in new ways. The only thing that's new here is that a different value might be given to the failure.Failure constructor and passed along the errback chain.</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">glyph> </span><span style="font-family:arial,sans-serif;font-size:13px">In fact in this case you almost want *multiple* inheritance, so you can say 'except CancelledError:' or 'except ConnectionError:' as appropriate.</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">If a deferred has been canceled and the errback fired, why can't code like `fail.check(CancelledError, ConnectionError, ...)` be used?</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">Sorry if I'm wrong/forgetful (I'm old), but it seems like lvh & glyph are talking about exceptions when they should be talking about handling Failure instances arriving via the errback chain.</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">A couple of comments:</span></div><div style><span style="font-family:arial,sans-serif;font-size:13px"><br>
</span></div><div style><span style="font-family:arial,sans-serif;font-size:13px"> - There are conceptually 3 places a deferred might be cancelled. 1. The code that makes the deferred might call cancel for some reason (e.g., service shutdown). 2. Intermediate code that called the code that creates the deferred and which passes the deferred on might cancel it. 3. The originating code (that uses a Twisted (or other library) API call) might decide to cancel it (e.g., due to timeout).</span></div>
<div style><br></div><div style><span style="font-family:arial,sans-serif;font-size:13px">In case 1, the documented interface of the API call can say what it does if it cancels a deferred. E.g., that a CancelledError failure will be delivered down the errback chain (the default behavior), or that some other kind of failure will be sent. A function could even allow the calling code to pass in a value that should be sent down the errback chain (wrapped in a Failure) when/if the deferred it creates is cancelled.  E.g. (pseudo-code):</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px"><br></span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">    def doSomething(cancelValue=None):</span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">        if cancelValue is not None:</span></div>
<div style><span style="font-family:arial,sans-serif;font-size:13px">            deferred = Deferred(lambda d: d.errback(failure.Failure(cancelValue)))</span></div><div style><span style="font-family:arial,sans-serif;font-size:13px">        else:</span></div>
<div style>            deferred = Deferred()</div><div style>        # Now do other things to arrange for 'deferred' to fire or fail.</div><div style>        return deferred</div><div style><br></div><div style>Case 2 is almost never going to happen. The intermediate code gets a deferred from someplace, maybe adds call/errbacks to it, and passes it on. If that code wants to hold on to the deferred it received and cancel it for some reason, it could do so, but it wont have any control over the failure value that comes down the errback chain.</div>
<div style><br></div><div style>Case 3 is more interesting, and is the main reason deferred cancelation was interesting me to (please see this thread for some background on how we got here: <a href="http://twistedmatrix.com/pipermail/twisted-python/2010-January/021298.html">http://twistedmatrix.com/pipermail/twisted-python/2010-January/021298.html</a> ). In this case, the original calling code wants to cancel the deferred. E.g., it has made a call to something that makes a network call and after some timeout decides it needs to proceed without the result. Due to its setup (using deferreds) the cleanest way for that code to proceed is to trigger the deferred itself. If it can do that, the normal (errback) error processing chain can simply handle the case where the deferred is cancelled.</div>
<div style><br></div><div style>A slight difficulty with the current situation is that code that obtains a deferred made by other code can't tell, if the deferred is cancelled, who cancelled it (or why). That includes the case where the code that received the deferred cancels it itself. I.e., if the code that makes the deferred cancels it (in the default way) or the code that receives the deferred cancels it (cases 1 and 3 above), the result is the same, a CancelledError in a failure.</div>
<div style><br></div><div style>In what I originally proposed (see above link), the caller could errback the deferred it was given *with a value of its choosing*. That would allow code to cancel a deferred and also to detect (in an errback) that it had done so and/or why (code might cancel a deferred for different reasons).  You can't do that with the implementation that actually landed in Twisted, though.</div>
<div style><br></div><div style><br></div><div style>A possible way to add some functionality / flexibility (including the above possibility) in a backwards-compatible way would be to allow Deferred.cancel to be called with a value argument (default=None to keep it backwards compat).  If no canceller was given to the Deferred constructor (or the canceller function did not fire the deferred), Deferred.cancel would call self.errback(failure.Failure(CancelledError(value))). In that way, anyone could cancel a deferred and the specific reason passed to cancel(), if any, would be available in the Python CancelledError instance as its first argument, as with any Python exception (i.e., args[0]).  In addition (backwards incompatible, though) the value passed to Deferred.cancel could also be passed to self._canceller, along with the deferred itself. I don't think that's needed though, as code doing more elaborate cancellation can set up any kind of Failure it wants to propagate back, can document its behavior, and can also allow for caller-specific values to be passed back (see code fragment above), etc.</div>
<div style><br></div><div style>OK, sorry if that's all a bit rambling & hard to follow...</div><div style><br></div><div style>Terry</div><div style><br></div></div><div class="gmail_extra"><br><br><div class="gmail_quote">
On Tue, Jun 18, 2013 at 8:54 PM, Glyph <span dir="ltr"><<a href="mailto:glyph@twistedmatrix.com" target="_blank">glyph@twistedmatrix.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div style="word-wrap:break-word"><br><div><div class="im"><div>On Jun 18, 2013, at 12:03 PM, Laurens Van Houtven <_@<a href="http://lvh.io" target="_blank">lvh.io</a>> wrote:</div><br><blockquote type="cite"><div style="letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px">
<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Tue, Jun 18, 2013 at 8:22 PM, Glyph<span> </span><span dir="ltr"><<a href="mailto:glyph@twistedmatrix.com" target="_blank">glyph@twistedmatrix.com</a>></span><span> </span>wrote:<br>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word">I would say that if we want to percolate this information up to the caller, there should be a ConnectingCancelled exception that is a subtype of the previous exception type.<div dir="auto">
</div></div></blockquote><div><br></div><div>Doesn't that mean we'll have many subclasses that mean that something was cancelled?<br><br>If I didn't take backwards compatibility into account, I would say that composing the original exception into a new CancellationError (or something) exception would be preferable. Would you agree that it would be preferable? (Again, not taking compatibility into account -- I'm trying to get compatibility vs niceness of API to face off against each other. Personally, I think it's enough of a change in functionality to warrant a chance in ways a function can fail, but there's no point in even having that argument if there's no consensus that the composed way would even be better...)<br>
</div></div></div></div></div></blockquote><div><br></div></div><div>I agree that it would be preferable, but I don't see how it's possible without making Exception itself composeable.</div><div class="im"><br><blockquote type="cite">
<div style="letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div> After all, if it's interesting that the operation was cancelled, presumably it's interesting <i>at what stage</i> the operation is cancelled.</div>
<div><br></div><div>IIUC that would work the same with composition as inheritance :)<br></div></div></div></div></div></blockquote></div></div><br><div>Unfortunately inheritance is built into the way Python handles exceptions.  In fact in this case you almost want *multiple* inheritance, so you can say 'except CancelledError:' or 'except ConnectionError:' as appropriate.  :-(</div>
<div><br></div><div>The one saving grace here is that not a whole lot of useful logic can live on the exception objects, so there's a limited amount of opportunity for getting oneself into trouble.</div><div><br></div>
<div>Please prove me wrong, though.</div><span class="HOEnZb"><font color="#888888"><div><br></div><div>-glyph</div><div><br></div></font></span></div><br>_______________________________________________<br>
Twisted-Python mailing list<br>
<a href="mailto:Twisted-Python@twistedmatrix.com">Twisted-Python@twistedmatrix.com</a><br>
<a href="http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python" target="_blank">http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python</a><br>
<br></blockquote></div><br></div>