The example was not attached. Could you please post that again?
Okay, I think that there are actually two separate issues here.
Catching signals, and the sometimes missing KeyboardInterrupt.
So to figure out what is going on with the signals I hacked up the
simple sample (attached) and played around with it. This was done in
my current CVS worksapce (2.5.3.x) with a wxGTK2 unicode build. I
first experimented with SIGUSR1 just to completly avoid the
SIGINT/KeyboardInterrupt issues. As I expected I found that:
* Sending the signal within the same with os.kill(pid, SIGUSR1)
shows the signal handler being called (seemingly) immediately.
* Sending the signal for outside the app with "kill -USR1 pid"
command shows that the handler is not called immediatly, but only
after some python code executes in an another event handler, (such as
clicking the Just for Fun button in this example.) This is because
when the signal happens Python doesn't call the handler immediately,
but just schedules it to be called later in between atomic python
opcodes.
* Adding a timer to the app to ensure that there are some Python
opcodes executing fairly often allows the external signal to be
handled fairly quickly after it happens.
The main reason I did this was to determine if I should be calling
PyErr_CheckSignals after every event handler, or perhaps after every
method call. I noticed that the patch you referenced hasn't been
applied to Python after a coupel years, and it is only called very
sparingly from within Python itself, so I was concerned about
possible implications of doing it so often from wxPython. But it
appears to me to not be needed because in either of those cases
(events or method calls) there will be some Python opcodes executed
either just before or just after anyway so we can let Python take
care of the signals itself. The only thing that could help is to
call PyErr_CheckSignals from within a custom wx.App.MainLoop, but I
think that may cause more troubles than it solves, especially since
it can be solved just by adding a timer to the app like I did.
Okay, now on to SIGINT. I added a custom handler for it just like I
did with SIGUSR1 and found that it worked exactly like SIGUSR1, as
expected. I then added a "raise KeyboardInterrupt" to the handler to
make it equivallent to default_int_handler. Here are my findings:
* Sending the signal from the same app with os.kill(pid, SIGINT) will
allow the exception to be raised back to the event handler that did
the kill. It then gets printed upon returning from the event handler
to the C++ wrapper code where I do a PyErr_Print(); Based on the
results above this was expected since there is nothing but Python
code between the sending of the signal to the catching of the
exception.
* Sending the signal externally (with the timer still in place) shows
that the signal is being caught and the handler is called, but the
exception is getting lost. This is undoubtedly due to my calling
PyErr_Clear in various places in wxPython and is probably the root of
the problem from your perspective.
Unfortunatly some of those PyErr_Clear's are needed because some
things that the wxPython wrappers do internally will cause an
exception upon errors, but I then handle the error myself so I clear
it. I'll look furhter into it though to see if some of the
PyErr_Clear's can be removed and if there is some way around the
others...
More comments below:
Jean Brouwers wrote:
Robin,
Before answering your specific questions, here is a description of
what we have been able to accomplish in the mean time. Major
progress, but no cigar yet.
In order to minimize the number of changes, we rebuilt wxPython
2.4.1.2 with just the call to PyOS_FiniInterrupt() commented out.
That made quite a difference, since signal.signal() calls no longer
throw exceptions!
In addition, we do set the signal handler for SIGINT to SIG_IGN
right before calling MainLoop(), just like the clearSigInt argument
does in wxPython 2.5.x. Plus, before invoking each command in our
modified Py Shell, we set the SIGINT handler to the Python
default_int_handler. Rigth after the command returns, we set the
SIGINT handler back to SIG_IGN.
This allows us to use Ctrl+C to interrupt long running commands,
reliably and without crashes. Second major improvement. The only
caveat is that Ctrl+C must be entered in the xterm window where the
application was started from, but we can live with that.
There are three new issues, however. The first one is that raising
an exception from within a wxPython key event handler does not seem
to have any effect at all. Some of our commands call a method
pending() which in turn calls wx.YieldIfNeeded(). Certain key
events like Ctrl+D set a flag and the pending() method throws the
KeyboardInterrupt if that flag is set. However, that
KeyboardInterrupt exception does not terminate the original command
which called pending(). It seems that the exception is caught by
the wxPython event manager or maybe by wx.YieldIfNeeded() and never
gets raised past that point.
After every call to an event handler or other callback wxPython does
a PyErr_Print call which prints the traceback and clears the error.
This is done because there is not an easy way to propogate the
exception past the C++ layer back up to the previous Python layer.
This has been discussed in the past as the Python/C++ sandwhich,
which is basically alternating layers of Python and C++ code on the
stack. There probably are ways to do it with C++ exceptiuons but it
will increase the complexity of the wrappers by probably an order of
magnitude, and wxWidgets probably still isn't fully exception safe.
The second problem is that using Ctrl+C while checking for pending()
interrupts has no effect either. Note, the signal handler for
SIGINT is the default_int_handler and not SIG_IGN! But typing
Ctrl+C does nothing inside wx.YieldIfNeeded(), the key event handler
for Ctrl+C is never called. But the key event handler for Cltr+D or
other keys is.
As a result, we need to have two different keys to interrupt
commands: Ctrl+C in the xterm window and Cltr+D in the wxPython
window. We can live with that too, but it would be better if that
were just a single Cltr+C key stroke.
Finally, the third issue is that we tried to use the GTK option
'--g-fatal-warnnings' to determine where certain GTK warnings
originate from. That particular option causes GTK to call abort()
when it prints a 'fatal warning' message. We install a Python
signal handler to catch the SIGABRT signal and that part works fine
now. But after GTK prints the warning and calls abort(), our
handler in Python is not called. Instead, the application crashes
with a core dump. This is not a show stopper at this moment, but it
indicates that there is still an issue with calling Python signal
handlers**.
Read the man page for abort(). It says that the abort() function
causes abnormal program termination unless the signal SIGABRT is
caught and the signal handler does not return. The Python C function
used to catch the signals and to set things up to invoke the handler
funciton later *always* returns and so abort() will *always* cause
the app to terminate and to dump a core file.
In summary, removing the call to PyOS_FiniInterrupt() is major
progress. There are still other improvements to be made, but we do
have a workable, albeit slightly clumsy system at this moment.
I had an idea on a separate approach that you could take that might
be smoother for you.if you had a separate app that was just a button,
(or perhaps a taskbar icon) that is spawned by your main app, then it
would have it's own event loop and would not get blocked by long
running things in the main app. When that button is activated it can
send a signal to the parent app with os.kill, and the handler in the
parent can do whatever is needed to interrupt the long running thing.
Just a thought.
/Jean Brouwers
ProphICy Semiconductor, Inc.
**) Our take is that wxPython doesn't allow Python to do its normal
signal handling routine, but we may be wrong on that. Same
diclaimer as before. Python handles signals in a special,
'synchronous' fashion. A lower level signal handler in C just marks
that a signal did occur (was tripped), unless the signal is ignored
or handled by default. Then, at some later time, Python C function
PyErr_CheckSignals() is called and it calls the Python handlers
installed for any tripped signals. The doc is here
<http://www.gmu.edu/mlnavbar/webdev/doc-python/api/exceptionHandling.h
t ml>
We grep'ed all wxPython source code and there is nowhere any call to
PyErr_CheckSignals() and we believe that that will be necessary. It
would be possible to call PyErr_CheckSignals() somewhere in our
code, like at the end of a command. But that requires an extension
of the Python signal module or better a wxPython provided interface
the PyErr_CheckSignals() function. Amybe wxPython should handle
this directly, like other packages do. Two examples are
<http://mail.python.org/pipermail/patches/2003-November/013771.html>
and
<Python download | SourceForge.net
_ ssl.c?rev=1.19>
Robin Dunn wrote:
Jean Brouwers wrote:
Robin,
We have been looking further into the crashes on SIGINT and other
signals, especially after a recent post from Fernando Perez at
comp.lang.python*.
Here is a summary and our conclusions, but with the disclaimer
that we have limited understanding of the inner workings of Python
and wxPython. This is a serious issue since it not only affects
SIGINT but all signals and renders the Python signal module almost
useless with wxPython.
After import wxPython, setting the signal handler for SIGINT back
to the Python default_int_handler seems to be working. It
generates a SystemError exception which is ignored but which
should not happen, obviously.
Does the exception happen when you set SIGINT or when you press
Ctrl-C?
Setting the signal handler for SIGINT back to SIG_IGN or setting
any signal handler to SIG_IGN or SIG_DFL also results in an
exception, but a TypeError.
IIRC, I think that the TypeError is because the signal extension
module is mixing types. It is taking the current handler and
returning it as if it was a Python function, but once you've set
IGN or DFL then the value is an integer flag and not a pointer to a
function.
Hitting Cltr+C at that time results in a crash, consistently and
repeatably.
When you press Ctrl-C or when the app exits? (The segfault at exit
was the issue that prompted the signal deactivation changes in the
first palce.)
It is unclear what the actual signal handler is for SIGINT and
what is causing the TypeError and SystemError, but it is quite
clear that both are symptoms of an underlying problem.
The traceback** indicates that the crash occurs at Py_INCR(x) in
the Python source file Python/ceval.c on line 2493 in function
PyEval_EvalCodeEx(). The value of one of the args is NULL instead
of the handler for SIGINT.
See above. SIG_IGN and SIG_DFL are integers, but Python is trying
to use the return value from signal() as a pointer and doesn't
check for SIG_IGN or SIG_DFL. (Unless something has changed since
I last traced through that code.)
Note, this is after the SIGINT handler is reset to SIG_IGN or
SIG_DFL.
Also, wxPython function __wxPreStart() in file
wxPython/src/helpers.cpp calls Python function
PyOS_FiniInterrupts() which is in the
You should try updating at least to 2.4.2.4, __wxPreStart was
changed by then and doesn't call PyOS_FiniInterrupts().
---------------------------------------------------------------------
To unsubscribe, e-mail: wxPython-users-unsubscribe@lists.wxwidgets.org
For additional commands, e-mail:
wxPython-users-help@lists.wxwidgets.org