We are trying to be compatible with Python's attribute errors here.
The real bug seems to be not printing out where the exception was
suppressed (e.g. due to a cdef method without an except clause needed
to propagate the exception up the call stack). Could you point me to a
ticket number so I can investigate?
- Robert
These messages are written by PyErr_WriteUnraisable, which is a
CPython C API function that writes unraisable exceptions. There are
typically two reasons for unraisable exceptions:
1) as Robert mentioned, a function that does not allow propagation
of exceptions, e.g.
cdef int func():
raise Exception
Here there is no way to propagate the raised exception, so
instead one should write something like
cdef int func() except -1: ...
Alternatively one may use 'except *' in case there is no error
indicator and Cython should always check, or "except ? -1" which means
"-1 may or may not indicate an error".
2) in deallocators or finalizers (e.g. __dealloc__ or __del__)
For functions the right thing is to add an except clause, for
finalizers and destructors one could use the traceback module, e.g.
try:
...
except:
traceback.print_exc()
If this all still doesn't help, try setting a (deferred) breakpoint on
__Pyx_WriteUnraisable or PyErr_WriteUnraisable.
Actually, I don't see why the default is to write unraisable
exceptions. Instead Cython could detect that exceptions may propagate
and have callers do the check (i.e. make it implicitly "except *").
Was this not implemented because Cython only knows whether functions
may propagate exceptions at code generation time by looking at the
presence of an error label?
Maybe it could keep code insertion points around for every call to
such a potential function and if the function uses the error label
have the caller perform the check? Although I do forsee problems for
external such functions... maybe Cython could have it's own
threadstate regardless of the GIL which would indicate whether an
error has occurred? e.g. CyErr_Occurred()?
Yes, I just told you, add a breakpoint to PyErr_WriteUnraisable. So basically:
$ gdb --args python -c 'import mymodule'
(gdb) break PyErr_WriteUnraisable
(gdb) run
No, Mark already pointed you to gdb.
>> There are typically two reasons for unraisable exceptions:
>> ...
>> cdef int func() except -1: ...
>> ...
>> For functions the right thing is to add an except clause, ...
>
> That is exactly what I'd love to do -- but the point is that I have
> absolutely no information whatsoever on the location of the error.
> WHERE should I add an except clause? WHAT cdef function/method needs
> an "except -1:", "except ?:" or whatever is appropriate?
>
> Exception AttributeError: 'PolynomialRing_field_with_category'
> object has no attribute '_modulus' in ignored
> is all what I get.
> "... in ignored" -- but where???
>
> It does not tell whether it is a finaliser/destructor, or a cdef
> function, or a cdef method, not the module in which the error occurs,
> not the name of the function -- simply nothing!
There's clearly something wrong here. The missing name may hint at a
destructor, as you mentioned, but it may just as well be something else.
gdb is your best bet. The "bt" command in gdb gives you a stack trace once
you've reached the breakpoint, that will tell you where the function gets
called.
Stefan
That's ok, it hasn't seen it yet because the shared library hasn't been
loaded yet. Just let it run through once, then set the breakpoint, then run
it again.
Stefan
Apparently 'sage' is a bash script (and gdb does not understand
scripts). I'm not sure how well following child processes will work
here, but sage has a -gdb switch. If you use that you can trap to gdb
using ^C, set a breakpoint and 'continue'.
Usually you use pending breakpoints, so
$ gdb --args bash `which sage`
(gdb) set breakpoint pending on
(gdb) break PyErr_WriteUnraisable
(gdb) run
I'm not sure if you would need 'set follow-fork-mode child' for that.
In fact, I'm not sure if this works when executing different
executables (bash executing python). In any case, I think sage -gdb
does what you want.
Nice job hunting this down.
The "exception ignored" message is actually generated by CPython, not
in Cython. That being said, CPython's default behavior is not the best
here, so perhaps we could mark __dealloc__ as not able to raise
exceptions and generate our own (incompatible, but only in terms of
its stdout side affect (? - we'd have to check this)) message.
- Robert
Well, I would suspect that Cython knows that it can't raise an exception
from within __dealloc__() because it generates a call to
PyErr_WriteUnraisable().
> and generate our own (incompatible, but only in terms of
> its stdout side affect (? - we'd have to check this)) message.
Yes, I think a reimplementation of PyErr_WriteUnraisable() that provides
more helpful context information would be better. It could even print a
full stack trace. After all, the mere fact that PyErr_WriteUnraisable() is
getting called at all is a very good indicator for a bug in the code. Any
additional information that we can give the users may help in fixing it
more quickly.
Stefan
I was talking about the actual __dealloc__ implementation called by tp_dealloc.
>> and generate our own (incompatible, but only in terms of
>> its stdout side affect (? - we'd have to check this)) message.
>
> Yes, I think a reimplementation of PyErr_WriteUnraisable() that provides
> more helpful context information would be better. It could even print a
> full stack trace. After all, the mere fact that PyErr_WriteUnraisable() is
> getting called at all is a very good indicator for a bug in the code. Any
> additional information that we can give the users may help in fixing it
> more quickly.
Sounds good to me.