Cython and Python signal

291 views
Skip to first unread message

Thierry

unread,
May 11, 2013, 5:15:14 PM5/11/13
to sage-...@googlegroups.com
Hi,

it was reported in
http://ask.sagemath.org/question/2567/kill-the-thread-in-a-long-computation
that cython seems not to handle Python signals correctly. It-it a
feature or should-it be reported ?

Ciao,
Thierry

Nils Bruin

unread,
May 11, 2013, 9:19:44 PM5/11/13
to sage-devel
On May 11, 2:15 pm, Thierry <sage-googlesu...@lma.metelu.net> wrote:
> Hi,
>
> it was reported inhttp://ask.sagemath.org/question/2567/kill-the-thread-in-a-long-compu...
> that cython seems not to handle Python signals correctly. It-it a
> feature or should-it be reported ?

I think it's a feature. There's a lot of code that reacts badly to
being interrupted by signals. If I'm not mistaken, most of our code
runs with signals disabled. It's the responsibility of the library
function authors to insert sig_on and sig_off things to allow
occasional signals to be processed when doing so is safe. Apparently
that didn't happen for `factor`!

William Stein

unread,
May 11, 2013, 10:10:58 PM5/11/13
to sage-...@googlegroups.com
No, it did happen with factor (in fact, that might be one of the first
functions that motivated me to write the first sig_on/sig_off). If
you look at the ask.sagemath discussion, you'll see everything got
sorted out, and mainly there was a mistake in the original poster's
code. They had written something silly and got confused about the
order of evaluation.

William

leif

unread,
May 11, 2013, 11:05:00 PM5/11/13
to sage-...@googlegroups.com
Nope, they had to resort to using @fork.

Try e.g.:

sage: alarm(10); factor(next_prime(2^300)*next_prime(2^400))

(This uses sage.misc.misc.alarm(), which installs a handler raising a
KeyboardInterrupt, after the specified number of seconds, but that does
no longer work as expected, at least not in conjunction with factoring
using the PARI library. You can of course still *manually* interrupt
the computation, and *after* doing so, the SIGALRM gets delivered, or
perhaps just the second KeyboardInterrupt /handled/, but I think the
first thing happens, i.e., apparently sig_on() [or PARI] block SIGALRM.)


-leif

--
() The ASCII Ribbon Campaign
/\ Help Cure HTML E-Mail

William Stein

unread,
May 12, 2013, 12:44:30 AM5/12/13
to sage-...@googlegroups.com
Many thanks for the clarification!

And indeed, if after typing "set_alarm" you send Sage (while
factoring) a SIGALRM signal by doing "kill -14 pid" from another
shell, it ignores it. Maybe sig_on/sig_off watches for SIGINT, not
for SIGALRM. I know that when I wrote the first version of
sig_on/sig_off, I choose a few specific signals (I think SIGINT and
floating point/math exceptions, etc.) to watch for and nothing else.
And don't know what Jereon did when he rewrote it.

If you don't do set_alarm first (so the Python signal handler isn't
changed to the one that raises a KeyboardInterrupt), then when running
the factor code, Sage will terminate (rather than ignoring it). I.e.,
SIGALARM is not ignored.

By the way, the alarm function in Sage uses Python's "signal.alarm",
whose source code is this:

static PyObject *
signal_alarm(PyObject *self, PyObject *args)
{
int t;
if (!PyArg_ParseTuple(args, "i:alarm", &t))
return NULL;
/* alarm() returns the number of seconds remaining */
return PyInt_FromLong((long)alarm(t));
}

Nils Bruin

unread,
May 12, 2013, 3:21:11 AM5/12/13
to sage-devel
On May 11, 9:44 pm, William Stein <wst...@gmail.com> wrote:
> By the way, the alarm function in Sage uses Python's "signal.alarm",
> whose source code is this:
>
> static PyObject *
> signal_alarm(PyObject *self, PyObject *args)
> {
>     int t;
>     if (!PyArg_ParseTuple(args, "i:alarm", &t))
>         return NULL;
>     /* alarm() returns the number of seconds remaining */
>     return PyInt_FromLong((long)alarm(t));

Probably more importantly, the signal handler that misc.alarm installs
is:

def __mysig(a,b):
raise KeyboardInterrupt, "computation timed out because alarm was
set for %s seconds"%__alarm_time

Given that installing this handler delays the handling of the SIGALRM,
I guess what happens is the normal thing in python exception handlers:
Some flags get set to remember that a signal has occurred and the next
time the interpreter sees fit to check, the signal gets handled (by
the routine that Of course, when this happens during a long
computation in the PARI library, one can wait a LONG time before the
flag gets checked.

The sig_on/sig_off aware signal handlers that get installed on SIGINT
etc. (but not on SIGALRM!) handle things differently. If we want
SIGALRM to be handled similarly to SIGINT when activated, we should
probably make the code in interrupt.c aware of a flag of some sort
that enables trapping/acting on SIGALRM.

Jeroen Demeyer

unread,
May 12, 2013, 4:20:47 AM5/12/13
to sage-...@googlegroups.com
Short answer: SIGALRM doesn't use the standard csage interrupt handling,
therefore it doesn't work in Cython code:

http://trac.sagemath.org/sage_trac/ticket/13311

leif

unread,
May 12, 2013, 7:17:51 PM5/12/13
to sage-...@googlegroups.com
Nils Bruin wrote:
> Probably more importantly, the signal handler that misc.alarm installs
> is:
>
> def __mysig(a,b):
> raise KeyboardInterrupt, "computation timed out because alarm was
> set for %s seconds"%__alarm_time
>
> Given that installing this handler delays the handling of the SIGALRM,
> I guess what happens is the normal thing in python exception handlers:
> Some flags get set to remember that a signal has occurred and the next
> time the interpreter sees fit to check, the signal gets handled (by
> the routine that Of course, when this happens during a long
> computation in the PARI library, one can wait a LONG time before the
> flag gets checked.

It doesn't seem Python would execute *any* code of the custom (Python)
handler (as opposed to just deferring the raise KeyboardInterrupt or
what follows it) before the Cython function returns by other means.


> The sig_on/sig_off aware signal handlers that get installed on SIGINT
> etc. (but not on SIGALRM!) handle things differently. If we want
> SIGALRM to be handled similarly to SIGINT when activated, we should
> probably make the code in interrupt.c aware of a flag of some sort
> that enables trapping/acting on SIGALRM.

Well, there are certainly also other use cases of signal.alarm() (or
more generally, SIGALRM) than aborting some computation, so while we
might (easily, I think) implement some
abort_next_computation_if_it_doesnt_finish_within(), also working with
Cython functions (or foreign functions they call), this could badly (and
presumably rather unexpectedly) interfere with user code (or, perhaps
less problematic, other libraries used). So at the very least, we
should not /unconditionally/ treat SIGALRM as if it was SIGINT.

In the meantime, the documentation of sage.misc.misc.alarm() should
probably get clarified, perhaps hinting at @fork as well.

Nils Bruin

unread,
May 12, 2013, 8:41:11 PM5/12/13
to sage-devel
On May 12, 4:17 pm, leif <not.rea...@online.de> wrote:

> It doesn't seem Python would execute *any* code of the custom (Python)
> handler (as opposed to just deferring the raise KeyboardInterrupt or
> what follows it) before the Cython function returns by other means.

Looking at the Python code base (and using common sense), I don't
think Python ever executes any python interpreter code *in the signal
handler*. Rather, the "signal occurred" information gets stored and
next time the interpreter gets around to it, all routines registered
with the relevant signal get executed, by then in the normal execution
context.

This still happens now as well: When the alarm arrives, the right data
gets set. If, in addition, a SIGINT arrives inside a sigon/sigoff, the
cython routine exits under exception conditions. However, as you see
from the message that gets processed, the signal condition is checked
by python before the exception condition and the signal handler code
gets executed and raises a KeyboardInterrupt with a custom message,
and that's the one that arrives.

So, one mode of operation would be:

Inside a sigon/sigoff, set the usual Python "signal occurred"
information. Furthermore, raise (as usual) an exception at the
outermost "sigon" active. That exception only serves the purpose as a
short path to the closes Python interpreter, which will process the
"signal occurred" information.

This would work slightly differently from the current sigon/sigoff
processing for SIGINT, which raises an exception but does not call
PyErr_SetInterrupt(), so presently, the signal gets processed as an
exception, not via Python's signal handling.

Incidentally, note:

sage: alarm(10); integrate(cos(x*sin(x)^10),x)
KeyboardInterrupt: computation timed out because alarm was set for 10
seconds

so somehow the SIGALRM gets reacted on properly by ECL (which has its
own signal handler) and somehow the condition arrives in its proper
state in Python as well, so that Python gets a turn at running the
registered signal handlers.

Cheers,

Nils

leif

unread,
May 12, 2013, 9:50:06 PM5/12/13
to sage-...@googlegroups.com
Nils Bruin wrote:
> Incidentally, note:
>
> sage: alarm(10); integrate(cos(x*sin(x)^10),x)
> KeyboardInterrupt: computation timed out because alarm was set for 10
> seconds
>
> so somehow the SIGALRM gets reacted on properly by ECL (which has its
> own signal handler) and somehow the condition arrives in its proper
> state in Python as well, so that Python gets a turn at running the
> registered signal handlers.

----------------------------------------------------------------------
| Sage Version 5.8, Release Date: 2013-03-15 |
| Type "notebook()" for the browser-based notebook interface. |
| Type "help()" for help. |
----------------------------------------------------------------------
sage: alarm(10); integrate(cos(x*sin(x)^10),x)
/usr/local/bin/sage: line 140: 30247 Segmentation fault
"$SAGE_ROOT/spkg/bin/sage" "$@"

:-)

With Sage 5.10.beta1, (the custom SIGALRM handler gets called as
expected and) I get a traceback, but only after much more than 10
seconds -- I'd say exactly *after* maxima_eval() returned [normally],
which is (almost) the same behaviour as with factor() using the PARI
library.

Nils Bruin

unread,
May 13, 2013, 1:46:17 AM5/13/13
to sage-devel
On May 12, 6:50 pm, leif <not.rea...@online.de> wrote:
> With Sage 5.10.beta1, (the custom SIGALRM handler gets called as
> expected and) I get a traceback, but only after much more than 10
> seconds -- I'd say exactly *after* maxima_eval() returned [normally],
> which is (almost) the same behaviour as with factor() using the PARI
> library.

Confirmed. Never mind.
Reply all
Reply to author
Forward
0 new messages