Hi,
As itamar mentioned in ticket #6676, If a cancellation function for a Deferred throws an exception(the
cancel()
method ofDeferred
won’t throw exceptions, but the canceller may), behavior is undefined. If the cancellation function throws an exception it is currently not caught, and cancellation does not occur.We can catch the exception and log it, and fallback to just firing
Deferred
withCancelledError
. This won’t break any old code. But an exception raising from the cancellation function often means the cancellation is failed.Another option we have is taking this opportunity to make the cancellation being able to fail. There is the motivation:
There are cases where a
Deferred
is uncancellable. For example, we can calltwisted.mail.imap4.IMAP4Client.delete
to delete a mailbox. When the operation is waiting in the queue, we can cancel it by removing it from the queue. However, when the operation is already sent and is waiting for the response, it becomes uncancellable.If we allow the canceller(NOT the
cancel()
method of theDeferred
) to raise an exception, we can tell the user the cancellation is failed and theDeferred
won’t be fired with aCancelledError
.Raising an exception from
cancel()
may break the old code. So we can catch the exception raised by the canceller, then return aFalse
without firing theDeferred
to tell the user that the cancellation is failed.In order to avoid missing unexpected exceptions, we can create a
CancellationFailedError
. When the canceller raisesCancellationFailedError
, we catch it and returnFalse
. When the canceller raises others exceptions, we catch it, log it then returnFalse
.
Something like this:
def cancel(self): if not self.called: canceller = self._canceller if canceller: try: canceller(self) except CancellationFailedError: return False except Exception: log.err(None, "Unexpected exception from canceller.") return False else: # Arrange to eat the callback that will eventually be fired # since there was no real canceller. self._suppressAlreadyCalled = True if not self.called: # There was no canceller, or the canceller didn't call # callback or errback. self.errback(failure.Failure(CancelledError())) return True elif isinstance(self.result, Deferred): # Waiting for another deferred -- cancel it instead. return self.result.cancel() else: return False
This won’t break any code by raising an exception from
cancel()
, although some code may rely oncancel()
not returning any value.So, what’s your opinion on raising an exception from the canceller?
Regards,
-Kai
_______________________________________________
Twisted-Python mailing list
Twisted-Python@twistedmatrix.com
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python