Opened 4 years ago

Closed 4 years ago

#5178 defect closed wontfix (wontfix)

Traceback object removed from Failure when it reaches end of Deferred callback chain

Reported by: forrestv Owned by: forrestv
Priority: normal Milestone:
Component: core Keywords: defer
Cc: Branch:
Author: Launchpad Bug:


(Moved from mention in #3622)

When a Deferred is errback'd with no handlers currently in the callback chain, the Failure is cleaned (with cleanFailure) and then stored in the Deferred's DebugInfo. However, the Deferred's Failure is not copied, but instead used for this, mutating it.

If the Deferred is used in an @inlineCallbacks function and caught as a normal exception, it then has a useless traceback:

import traceback

from twisted.internet import defer

def a():

def b():
        result = yield defer.maybeDeferred(a)
    except Exception:
        print result


results in

Traceback (most recent call last):
  File "", line 11, in b
    result = yield defer.maybeDeferred(a)
ZeroDivisionError: integer division or modulo by zero

Attachments (1)

5178.diff (3.2 KB) - added by forrestv 4 years ago.
patch to duplicate the Deferred's Failure before cleaning it to store it in DebugInfo

Download all attachments as: .zip

Change History (6)

comment:1 Changed 4 years ago by forrestv

  • Type changed from enhancement to defect

Changed 4 years ago by forrestv

patch to duplicate the Deferred's Failure before cleaning it to store it in DebugInfo

comment:2 Changed 4 years ago by forrestv

  • Keywords defer review added

comment:3 Changed 4 years ago by exarkun

  • Keywords review removed
  • Owner set to forrestv

Thanks for your interest in this area, forrestv.

I think the solution in the patch is problematic. The rest of the _runCallbacks code takes great care to ensure that an uncleaned Failure will not last past the end of the callback processing. It is not an accident that the Failure on the Deferred is shared with the _DebugInfo and that it gets cleaned, it is intentional.

If the Failure remains uncleaned, then it keeps references to every local on the call stack at the time of the exception. This has terrible consequences for memory usage and cyclic garbage creation.

The issue described here, that tracebacks associated with Deferreds with failure results are not always fully informative, is real. However, limitations in the Python runtime mean that it is very, very difficult to fix this. On the one hand I want to close this ticket as wontfix (or cantfix, but we don't have that status). On the other hand, it is a real shortcoming, and it would be nice if someone figured out a real way to fix it.

Another approach you might consider, though, is that maybeDeferred only serves to introduce this problem in this particular example. Stop using it and the problem goes away:

import traceback

from twisted.internet.defer import inlineCallbacks

def bad():
    raise RuntimeError("foo")

def good():
    return 42

def foo():
    for f in good, bad:
            result = yield f()
            print 'Failure'
            print 'Success', result

produces output:

Success 42
Traceback (most recent call last):
  File "", line 16, in foo
    result = yield f()
  File "", line 7, in bad
    raise RuntimeError("foo")
RuntimeError: foo

comment:4 Changed 4 years ago by radix

also, if you use log.err() instead of traceback.print_exc(), you'll get a better traceback, even if you do use maybeDeferred.

comment:5 Changed 4 years ago by exarkun

  • Resolution set to wontfix
  • Status changed from new to closed

Thanks again for your interest. In the light of the above comments, I think I will close this as wontfix. Improvements to Failure are welcome, but they need to be made carefully and with consideration of Failure's more subtle (often undocumented) goals. I encourage you to raise the issue on the twisted-python mailing list or the #twisted irc channel on freenode.

Note: See TracTickets for help on using tickets.