Saturday, June 16, 2007

Why can't the pydev debugger work with turbogears?

Ok, there's a problem that can't really be overcome in the pydev debugger when using turbogears... (just doing "import turbogears" would already break it).

Actually, no OPTIMIZED debugger would be able to work with that. I'm saying optimized because the implementation seems to take into account naive debuggers which would trace all the calls within all the frames (pydev only traces frames with breakpoints).

The problem is: there's a module that turbogears uses (in my tests: DecoratorTools-1.4-py2.5.egg) which has a decorator named: decorate_assignment. This decorator uses the tracing facility that python provides for debuggers and removes the current debugger tracer function. It still tries to restore it if it was tracing the frame previously (but that would hardly ever happen in an optimized debugger).

So, there's no way to actually fix that from pydev, but there are some options to make it work:

1. Using the pydev extensions remote debugger (but if that decorator is called after the remote debugger is set, the debugger would stop working again, so, this option would only useful if that decorator is not used later).

2. Removing that decorator from the places that use it in turbogears (the implications for that would have to be checked).

3. Hard-coding it to return the pydev tracing function. To do that, the file: DecoratorTools-1.4-py2.5.egg\peak\util\decorators.py must be changed so that the function "def decorate_assignment(callback, depth=2, frame=None):" does not use the call:


"oldtrace = [frame.f_trace]"


and uses the code below instead:


oldtrace = None
try:
    import pydevd
    debugger=pydevd.GetGlobalDebugger()
    if debugger is not None:
        oldtrace = [debugger.trace_dispatch]

except ImportError:
    pass

if oldtrace is None:
    oldtrace = [frame.f_trace]


The 3rd option is probably the easier in the short run for those wanting to debug turbogears in pydev, but I think that the 2nd should be the one actually used (as a general rule, I believe that only debuggers should play with the tracing facility, because it tends to bee way to instrusive, and it's probably the most un-optimized way of doing something, as you're going to trace all that happens, which can lead to a large overhead).

26 comments:

  1. Anonymous8:04 PM

    Ah, I was just wondering about that. Thanks!

    ReplyDelete
  2. Anonymous7:56 AM

    Have you talked with the author of Peak to get his suggestions? If this really is an oversight of the developer of that package , I'm sure he'd like to know.

    ReplyDelete
  3. Hello I tested successfully the 3rd option with python-2.4, DecoratorTools-1.4 and pydev 1.3.5
    But I have upgraded to python-2.5, DecoratorTools-1.5 and pydev 1.3.8
    and now, my application is not working when running in the debugger.
    I get the pydev warning about usage of sys.settrace() and the application start, I can click some link, but I looks like when I'am submiting forms, I get error:
    TypeError: ("'NoneType' object is not callable", <bound method Root.quick_login of <myapps.controllers.Root object at 0xa92ebac>>)
    Any idea ?

    ReplyDelete
  4. Hummm... no real idea... I haven't checked how DecoratorTools 1.5 is, but the 'patched code' may have to be changed somehow... also, don't you have a full stack trace for that exception?

    ReplyDelete
  5. == eclipse DecoratorTools-1.5 ==

    Their is no difference between DecoratorTools-1.4 and 1.5 except this

    > def enclosing_frame(frame=None, level=3):
    > """Get an enclosing frame that skips DecoratorTools callback code"""
    > frame = frame or sys._getframe(level)
    > while frame.f_globals.get('__name__')==__name__: frame = frame.f_back
    > return frame
    >
    356c356
    < frame = frame or sys._getframe(depth)
    ---
    > frame = enclosing_frame(frame, depth+1)


    Here are some message I have only in debuger mode

    2007-08-17 21:53:10,761 turbogears.visit DEBUG Loading visit manager from plugin: sqlalchemy
    /kolab/lib/python/threading.py:697: RuntimeWarning: tp_compare didn't return -1 or -2 for exception
    return _active[_get_ident()]
    2007-08-17 21:53:10,805 turbogears.visit INFO Visit filter initialised
    Exception exceptions.SystemError: 'error return without exception set' in <generator object at 0x9bb290c> ignored
    Exception exceptions.SystemError: 'error return without exception set' in <generator object at 0x9bb28ec> ignored
    ...
    Exception exceptions.SystemError: 'error return without exception set' in <generator object at 0x9bb2eac> ignored
    Exception exceptions.SystemError: 'error return without exception set' in <generator object at 0x9bb292c> ignored

    And here my stack trace

    URL: http://eg01.apps.loc:8080/eg/quick_login?user_name2=manager
    File '/kolab/lib/python/site-packages/Paste-1.4-py2.5.egg/paste/evalexception/middleware.py', line 308 in respond
    return_iter = list(app_iter)
    File '/kolab/lib/python/site-packages/CherryPy-2.2.1-py2.5.egg/cherrypy/_cpwsgi.py', line 75 in wsgiApp
    environ['wsgi.input'])
    File '/kolab/lib/python/site-packages/CherryPy-2.2.1-py2.5.egg/cherrypy/_cphttptools.py', line 72 in run
    self._run()
    File '/kolab/lib/python/site-packages/CherryPy-2.2.1-py2.5.egg/cherrypy/_cphttptools.py', line 105 in _run
    self.main()
    File '/kolab/lib/python/site-packages/CherryPy-2.2.1-py2.5.egg/cherrypy/_cphttptools.py', line 254 in main
    body = page_handler(*virtual_path, **self.params)
    File '<string>', line 3 in quick_login
    File '/kolab/lib/python/site-packages/TurboGears-1.0.3.2-py2.5.egg/turbogears/controllers.py', line 340 in expose
    _build_rules(func)
    File '/kolab/lib/python/site-packages/TurboGears-1.0.3.2-py2.5.egg/turbogears/controllers.py', line 246 in _build_rules
    for ruleinfo in func._ruleinfo:
    File '/kolab/lib/python/site-packages/TurboGears-1.0.3.2-py2.5.egg/turbogears/controllers.py', line 246 in _build_rules
    for ruleinfo in func._ruleinfo:
    TypeError: ("'NoneType' object is not callable", <bound method Root.quick_login of <apps.controllers.Root object at 0x9ab3ecc>>)

    ReplyDelete
  6. The problem is not related to my new eclipse with pydev 1.3.8 because I tried with my old environment, python-2.4, TG 1.0.2 and got no error.
    Then the problem is python2.5 or TG 1.0.3.

    ReplyDelete
  7. I believe the problem is related to python 2.5...

    I know the error below is a python 2.5 bug (and others may just follow it?)

    /kolab/lib/python/threading.py:697: RuntimeWarning: tp_compare didn't return -1 or -2 for exception

    I've added a bug about it to the python bugtracker, but got no responses about it... (maybe you can ask there...)

    https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1733757&group_id=5470

    Cheers,

    Fabio

    ReplyDelete
  8. It didn't post the link correctly... trying to put it is hyperlink

    link to sf tracker

    ReplyDelete
  9. Is this the correct link? it says "Only Group Members Can View Private ArtifactTypes."

    I am running into the exact same problem with Python 2.5 and pydev 1.3.8:
    C:\Python25\Lib\threading.py:698: RuntimeWarning: tp_compare didn't return -1 or -2 for exception
    return _active[_get_ident()]

    ReplyDelete
  10. Strange... it seems that it's no longer available... I've found a link to that in this link.

    But I don't know what happened to that bug... (did the python guys change their bug-tracking?)

    ReplyDelete
  11. Searching a little more... that bug can be found at: http://bugs.python.org/issue1733757

    ReplyDelete
  12. I get this error and I don't understand why. I'm not doing anything more complicated than plain python executed as CGI through apache.

    I wrote:
    sys.path.append("/usr/local/eclipse/plugins/org.python.pydev.debug_1.3.10/pysrc/")
    import pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True, suspend=True)

    The thing breaks correctly, and I can step, but the Eclipse debugger doesn't actually catch exceptions in the code. Apache's stderr says:

    %stderr

    PYDEV DEBUGGER WARNING:
    sys.settrace() should not be used when the debugger is being used.
    This may cause the debugger to stop working correctly.
    If this is needed, please check:
    http://pydev.blogspot.com/2007/06/why-cant-pydev-debugger-work-with.html
    to see how to restore the debug tracing back correctly.
    Call Location:
    File "/usr/local/eclipse/plugins/org.python.pydev.debug_1.3.10/pysrc/pydevd.py", line 743, in settrace
    sys.settrace(debugger.trace_dispatch)

    Traceback (most recent call last):
    File "/usr/local/eclipse/plugins/org.python.pydev.debug_1.3.10/pysrc/pydevd_frame.py", line 117, in trace_dispatch
    self.doWaitSuspend(t, frame, event, arg)
    File "/usr/local/eclipse/plugins/org.python.pydev.debug_1.3.10/pysrc/pydevd_frame.py", line 26, in doWaitSuspend
    self.mainDebugger.doWaitSuspend(*args, **kwargs)
    File "/usr/local/eclipse/plugins/org.python.pydev.debug_1.3.10/pysrc/pydevd.py", line 510, in doWaitSuspend
    frame.f_back.f_trace = GetGlobalDebugger().trace_dispatch
    AttributeError: 'NoneType' object has no attribute 'f_trace'
    Traceback (most recent call last):
    File "/usr/home/marauder/work/gtex/bookings/site/admin/zones.py", line 8, in >module>
    import page, database
    File "/usr/home/marauder/work/gtex/bookings/site/admin/page.py", line 16, in >module>
    filemode='a')
    File "/usr/local/lib/python2.5/logging/__init__.py", line 1240, in basicConfig
    hdlr = FileHandler(filename, mode)
    File "/usr/local/lib/python2.5/logging/__init__.py", line 770, in __init__
    stream = open(filename, mode)
    IOError: [Errno 2] No such file or directory: '/usr/home/marauder/work/gtex/bookings/logs/adminlog'

    ReplyDelete
  13. Anonymous12:59 PM

    I'm running into the same problem with Python 2.5 and getting errors like: "TypeError: 'NoneType' object is not callable"

    I've notice this occurs on functions in controllers.py with an empty @expose()

    A work-around which might not work for everyone is to replace it with @expose("json").

    ReplyDelete
  14. python 2.5.2 is out and I have tested
    http://bugs.python.org/issue1733757
    I dont have any error messages anymore.

    I tried debugging with pydev and turbogears but It looks like pydev detect the sys.settrace(tracer)
    an refuse to go further.

    Is is possible to bypass this protection ?

    ReplyDelete
  15. Actually, calling sys.settrace still works (it won't actually stop the execution when it happens... it just prints it to the screen).

    You can use pydevd_tracing.SetTrace to call the original tracing without having a warning message.

    But aside from that, you must still restore the tracing facility as specified in the post for the debugger to keep working (and it won't be able to debug code that happens when the tracing function is replaced).

    Cheers,

    Fabio

    ReplyDelete
  16. I notice you have code that's checking sys.settrace and issuing a warning; but if settrace() is being passed one of *your* trace functions (i.e., it's restoring the tracer, not replacing it), then why not just call the old settrace() with the correct function?

    ReplyDelete
  17. That's mostly a safeguard... I could make a check and don't warn if it's a debugger function, but I think that having a different interface is safer.

    Cheers,

    Fabio

    ReplyDelete
  18. What I mean is, you could safely restore PyDev's global trace function if settrace() is called with None, or with a local trace function. In other words, tracing a DecoratorTools-using app (or anything else that uses a similar technique) would then work correctly, except for the warning(s).

    ReplyDelete
  19. i,
    i tried to change the code as written in the blog here.
    I use an Mac OSX 10.4.11 and 2.5.2
    and Turbogears 1.0.5 Decorator Tools 1.7
    And debugging is not possible.
    I tried pydev

    Does anyone have an hint for me or same problem ?

    Or an link which i didnt found so far.

    Thanks in advance
    thomas

    ReplyDelete
  20. Phillip Frantz4:03 AM

    Just in case this is still an issue, Python 2.6 provides a sys.gettrace() function which means that instead of unconditionally trying to reset the settrace function to the pydev debugger tracer all you need to do in decorator tools is change the line:-

    oldtrace = [frame.f_trace]

    to

    oldtrace = [sys.gettrace()]

    and debugging should work again. This works fine with the latest version of decorator tools and turbogears 1.1. The solution originally outlined in this post won't work with versions of turbogears greater than 1.0.4 because of the way decorator assignment is being used in the peak Rules package.

    ReplyDelete
  21. Thanks for the tip... as it happens, I was able to implement a refactoring that might actually fix this for older Pythons as well (but definitely in 2.6). It's in SVN if you'd like to give it a try; I'd like some feedback before pushing it out as an official release.

    ReplyDelete
  22. I am using python 2.6 with Turbogears 1.5 and PEAK Rules 0.5a1 and DecoratorTools 1.7. The solution suggested Phillip Frantz doesn't work. The warning PYDEV DEBUGGER WARNING persists.

    If I try the solution by Fabio Zadrozny, I get the following error:

    File "/usr/local/lib/python2.6/dist-packages/PEAK_Rules-0.5a1.dev_r2600-py2.6.egg/peak/rules/core.py", line 657, in
    class MethodList(Method):
    TypeError: 'NoneType' object is not callable

    ReplyDelete
  23. Dolf: based on the comments above, the patch won't remove the warning but should make the breakpoints work.

    That said, I tried Philip's Patch and my test runs to completion - without stopping on any breakpoints.

    Fabio's patch from the post gives me the same None not callable problem. This is TurboGears 2 and DecoratorTools 1.8.

    ReplyDelete
  24. Anonymous4:03 AM

    I've found this issue, but turbogears is not installed in my virtualenv.

    PYDEV DEBUGGER WARNING:
    sys.settrace() should not be used when the debugger is being used.
    This may cause the debugger to stop working correctly.
    If this is needed, please check:
    http://pydev.blogspot.com/2007/06/why-cant-pydev-debugger-work-with.html
    to see how to restore the debug tracing back correctly.
    Call Location:
    File "/usr/lib/python2.7/bdb.py", line 225, in set_trace
    sys.settrace(self.trace_dispatch)

    ReplyDelete
  25. Well, the issue is that some library you're using is using the debugger tracing facility (which it shouldn't).

    i.e.: check your code/libraries for sys.settrace.

    ReplyDelete
  26. One of the places that uses "sys.settrace" that is pretty commonly used is the doctest module, so testing situations that also run doctest also will trigger the warning. It's not a big deal since we can't debug into doctests anyhow (that'd be a big challenge!) but it is common enough and hard to avoid. (I have an integrated testing situation that runs both doctests and unittests, and the debugging is generally done on the code the doctest runs, not the doctest itself). [PyDev is amazing!]

    ReplyDelete