I'm trying to cancel a Deferred
chain (main) whenever some chained deferred (child) raises an error.
But I'm getting an AlreadyCalledError
and the chain continues its work.
Here's the code:
from twisted.internet import defer
def asyncText(s, okCallback, errCallback):
deferred = defer.Deferred()
deferred.addCallbacks(okCallback, errCallback)
if s.find('STOP')>=0:
deferred.errback(ValueError('You are trying to print the unprintable :('))
else:
deferred.callback(s)
return deferred
def asyncChain():
texts = ['Hello StackOverflow,',
'this is an example of Twisted.chainDeferred()',
'which is raising an AlreadyCalledError',
'when I try to cancel() it.',
'STOP => I will raise error and should stop the main deferred',
'Best regards'
]
mainDeferred= defer.Deferred()
for text in texts:
def onSuccess(res):
print('>> {}'.format(res))
def onError(err):
print('Error!! {}'.format(err))
mainDeferred.cancel()
d = asyncText(text, onSuccess, onError)
mainDeferred.chainDeferred(d)
And here's the output:
>>> asyncChain()
- Hello StackOverflow,
- this is an example of Twisted.chainDeferred()
- which is raising an AlreadyCalledError
- when I try to cancel() it.
Error!! [Failure instance: Traceback (failure with no frames): <class 'ValueError'>: You are trying to print the unprintable :(
]
- Best regards
Unhandled error in Deferred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../test.py", line 1, in asyncChain
mainDeferred.chainDeferred(d)
File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 435, in chainDeferred
return self.addCallbacks(d.callback, d.errback)
File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 311, in addCallbacks
self._runCallbacks()
--- <exception caught here> ---
File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
current.result = callback(current.result, *args, **kw)
File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 501, in errback
self._startRunCallbacks(fail)
File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 561, in _startRunCallbacks
raise AlreadyCalledError
twisted.internet.defer.AlreadyCalledError:
I also tried to use a canceller
, like so:
def asyncOnCancel(d):
print('------ cancel() called ------')
d.errback(ValueError('chain seems to be cancelled!'))
def asyncChainOnError(err):
print('------ ERROR ON Chain {} ------'.format(err))
...
mainDeferred= defer.Deferred(canceller= asyncOnCancel)
mainDeferred.addErrback(asyncChainOnError)
...
But result is the same.
I also tried delaying child .callback(s)
calls, or calling them after the .chainDeferred()
. But I get always the same behaviour.
Deferred
(and getting chained child deferreds to get cancelled too)?AlreadyCalledError
?I'm using python 3.6.6 and Twisted 18.9.0.
Thanks!
******* EDIT *******
After Jean-Paul answer, and noticing that .chainDeferred()
is not what I need, I'll put here in a clearer way what I want (and how I finnally did it).
What I want was quite simpler: to run several Deferred, in a "synchronous" way (they have to wait for the previous one to have finished), although they don't need to share their results. If one fails, the rest of them is not executed.
Turns out is quite easy to do it with @defer.inlineCallbacks
and yield
. Here an example:
def asyncText(s):
deferred = defer.Deferred()
if s.find('STOP') >= 0:
deferred.callback(True)
else:
deferred.callback(False)
return deferred
@defer.inlineCallbacks
def asyncChain():
texts = ['Hello StackOverflow,',
'this is a simpler way to explain the question.',
'I need no chainDeferred(). I need no .cancel().',
'I just need inlineCallbacks decorator.',
'STOP',
'Yeah I will not be printed'
]
for text in texts:
stopHere = yield asyncText(text)
if stopHere:
break
print('- {}'.format(text))
deferred= asyncChain()
>>> asyncChain()
- Hello StackOverflow,
- this is a simpler way to explain the question.
- I need no chainDeferred(). I need no .cancel().
- I just need inlineCallbacks decorator.
It's not clear what you're actually trying to do but I think your chain is backwards from what you expect:
d = asyncText(text, onSuccess, onError)
mainDeferred.chainDeferred(d)
d
has already fired when asyncText
returns. But mainDeferred.chainDeferred(d)
means "when mainDeferred fires, pass its result to d". Since d
has already fired, this is invalid. A Deferred can only fire once.
Perhaps you meant d.chainDeferred(mainDeferred)
instead. Then "when d fires, pass its result to mainDeferred".
However, the there is still a problem since if you chain d
to mainDeferred
then it doesn't make sense to cancel mainDeferred
in a callback on d
. The result will propagate from d
to mainDeferred
because they're chained. Cancellation is not necessary or useful.