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).

23 comments:

Anonymous said...

Ah, I was just wondering about that. Thanks!

Anonymous said...

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.

aspineux said...

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 ?

Fabio Zadrozny said...

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?

aspineux said...

== 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>>)

aspineux said...

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.

Fabio Zadrozny said...

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

Fabio Zadrozny said...

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

link to sf tracker

debedb said...

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()]

Fabio Zadrozny said...

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?)

Fabio Zadrozny said...

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

marauder said...

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'

David G. said...

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").

Alain said...

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 ?

Fabio Zadrozny said...

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

PJE said...

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?

Fabio Zadrozny said...

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

PJE said...

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).

tkamsker said...

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

Phillip Frantz said...

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.

PJE said...

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.

Dolf said...

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

Dobes said...

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.