How to signal alarm in python 2.4 after 0.5 seconds
Asked Answered
A

2

9

I want to timeout a particular piece of python code after in runs for 0.5 seconds. So I intend to raise an exception/signal after 0.5 seconds, and handle it gracefully and continue with rest of code.

In python i know that signal.alarm() can set alarm for integer seconds. Is there any alternative where we can generate an alarm after 0.5 seconds. signal.setitimer() as suggested in other posts is not available in python2.4 and I need to use python2.4 for this purpose?

Acrylyl answered 23/11, 2011 at 12:52 Comment(3)
writing a C extension is one option...Thelen
To whoever voted to close this question supporting the idea is an exact duplicate of this one: have you actually read the OP question till its end? Question is fully valid IMO.Lope
@mac: I think the person who voted to close already figured out her mistake -- at least she deleted the automatically generated comment. :)Blaine
D
5

Raise the alarm from a patiently waiting "daemon" thread. In the code below, snoozealarm does what you want via a SnoozeAlarm thread:

#! /usr/bin/env python

import os
import signal
import threading
import time

class SnoozeAlarm(threading.Thread):
  def __init__(self, zzz):
    threading.Thread.__init__(self)
    self.setDaemon(True)
    self.zzz = zzz

  def run(self):
    time.sleep(self.zzz)
    os.kill(os.getpid(), signal.SIGALRM)

def snoozealarm(i):
  SnoozeAlarm(i).start()

def main():
  snoozealarm(0.5)
  while True:
    time.sleep(0.05)
    print time.time()


if __name__ == '__main__':
  main()
Drumhead answered 23/11, 2011 at 20:39 Comment(1)
That's what I call "reverse" engineering. +1 ;)Lope
B
2

You have two options:

  1. Polling time.time() or similar while the code in question runs. This is obviuosly only viable if that code is under your control.

  2. As mentioned by pajton, you could write a C extension to call the system call setitimer(). This isn't too hard because you could simply copy the code of signal.getitimer() and signal.setitimer() from the source of later versions of Python. They are just thin wrappers around the equally named system calls.

    This option is only viable if you are using CPython and you are in an environment that allows you to use custom C extensions.

    Edit: Here is the code copied from signalmodule.c in Python 2.7 (Python's licence applies):

    #include "Python.h"
    #include <sys/time.h>
    
    static PyObject *ItimerError;
    
    /* auxiliary functions for setitimer/getitimer */
    static void
    timeval_from_double(double d, struct timeval *tv)
    {
        tv->tv_sec = floor(d);
        tv->tv_usec = fmod(d, 1.0) * 1000000.0;
    }
    
    Py_LOCAL_INLINE(double)
    double_from_timeval(struct timeval *tv)
    {
        return tv->tv_sec + (double)(tv->tv_usec / 1000000.0);
    }
    
    static PyObject *
    itimer_retval(struct itimerval *iv)
    {
        PyObject *r, *v;
    
        r = PyTuple_New(2);
        if (r == NULL)
        return NULL;
    
        if(!(v = PyFloat_FromDouble(double_from_timeval(&iv->it_value)))) {
        Py_DECREF(r);
        return NULL;
        }
    
        PyTuple_SET_ITEM(r, 0, v);
    
        if(!(v = PyFloat_FromDouble(double_from_timeval(&iv->it_interval)))) {
        Py_DECREF(r);
        return NULL;
        }
    
        PyTuple_SET_ITEM(r, 1, v);
    
        return r;
    }
    
    static PyObject *
    itimer_setitimer(PyObject *self, PyObject *args)
    {
        double first;
        double interval = 0;
        int which;
        struct itimerval new, old;
    
        if(!PyArg_ParseTuple(args, "id|d:setitimer", &which, &first, &interval))
        return NULL;
    
        timeval_from_double(first, &new.it_value);
        timeval_from_double(interval, &new.it_interval);
        /* Let OS check "which" value */
        if (setitimer(which, &new, &old) != 0) {
        PyErr_SetFromErrno(ItimerError);
        return NULL;
        }
    
        return itimer_retval(&old);
    }
    
    PyDoc_STRVAR(setitimer_doc,
    "setitimer(which, seconds[, interval])\n\
    \n\
    Sets given itimer (one of ITIMER_REAL, ITIMER_VIRTUAL\n\
    or ITIMER_PROF) to fire after value seconds and after\n\
    that every interval seconds.\n\
    The itimer can be cleared by setting seconds to zero.\n\
    \n\
    Returns old values as a tuple: (delay, interval).");
    
    static PyObject *
    itimer_getitimer(PyObject *self, PyObject *args)
    {
        int which;
        struct itimerval old;
    
        if (!PyArg_ParseTuple(args, "i:getitimer", &which))
        return NULL;
    
        if (getitimer(which, &old) != 0) {
        PyErr_SetFromErrno(ItimerError);
        return NULL;
        }
    
        return itimer_retval(&old);
    }
    
    PyDoc_STRVAR(getitimer_doc,
    "getitimer(which)\n\
    \n\
    Returns current value of given itimer.");
    
    static PyMethodDef itimer_methods[] = {
        {"setitimer",       itimer_setitimer, METH_VARARGS, setitimer_doc},
        {"getitimer",       itimer_getitimer, METH_VARARGS, getitimer_doc},
        {NULL,                      NULL}           /* sentinel */
    };
    
    PyMODINIT_FUNC
    inititimer(void)
    {
        PyObject *m, *d, *x;
        int i;
        m = Py_InitModule3("itimer", itimer_methods, 0);
        if (m == NULL)
            return;
    
        d = PyModule_GetDict(m);
    
    #ifdef ITIMER_REAL
        x = PyLong_FromLong(ITIMER_REAL);
        PyDict_SetItemString(d, "ITIMER_REAL", x);
        Py_DECREF(x);
    #endif
    #ifdef ITIMER_VIRTUAL
        x = PyLong_FromLong(ITIMER_VIRTUAL);
        PyDict_SetItemString(d, "ITIMER_VIRTUAL", x);
        Py_DECREF(x);
    #endif
    #ifdef ITIMER_PROF
        x = PyLong_FromLong(ITIMER_PROF);
        PyDict_SetItemString(d, "ITIMER_PROF", x);
        Py_DECREF(x);
    #endif
    
        ItimerError = PyErr_NewException("itimer.ItimerError",
                                         PyExc_IOError, NULL);
        if (ItimerError != NULL)
            PyDict_SetItemString(d, "ItimerError", ItimerError);
    }
    

    Save this code as itimermodule.c, compile it to a C extension using something like

    gcc -I /usr/include/python2.4 -fPIC -o itimermodule.o -c itimermodule.c
    gcc -shared -o itimer.so itimermodule.o -lpython2.4
    

    Now, if you are lucky, you should be able to import this from Python using

    import itimer
    

    and call itimer.setitimer().

Blaine answered 23/11, 2011 at 13:18 Comment(13)
Absolutely untested: but what about running the code to be pruned if the signal occurs under a thread, and running a timer (polling time.time()) in the main code? The timer could kill the thread once the limit is hit... [ugly but... couldn't it work?]Lope
@mac: No, this doesn't work. You can't kill threads in Python.Blaine
@sven: the code will not be under my control. There are lots of computations and function calling involved, and it is difficult to poll at a single place.Acrylyl
I seldom if ever work with threads so I trust you on this! Yet, I was under the impression there were workaround (albeit with limitations) to actually achieve the killing...Lope
I am using cpython, and allowed to use c++ extensions (i use lib_boost) for certain computations. So please provide further detailAcrylyl
Creating threads with join(timeout) and killing them, is not viable for me because i need to do such 0.5 seconds operations for a very large number of times, and this could get a lot dirtyAcrylyl
@mac: Thanks for the link. I was aware of most things in that thread, but there were a few interesting new details in Bluebird75's post. (My previous comment is in line with Martin v. Löwis's answer in the linked question.) Bluebird75's way isn't viable here as well, since -- as far as I understand -- the code is busy in a C++ extension. Raising a Python exception won't interrupt the C++ code.Blaine
Yes, major part of the code is busy in few C++ modules exposed to python via libboost. So is there any other way, than to poll at different points in the code?Acrylyl
@NithinLingala: Added further detail on porting the setitimer() function to Python 2.4.Blaine
I tested this with Python 2.5 only, since I don't have a Python 2.4 installation around.Blaine
@Lope — the inverse of your suggestion ought to work. Put the alarming function, not the main code, in a "background" thread. See my answerDrumhead
@sven,pilcrow: Both the solutions are really good. But as sven said, these will be python exceptions and wont interrupt the C++ code. These solutions answer my initial question, but apologies, that I didnt know that python exceptions wont interrupt C++ codeAcrylyl
@NithinLingala: You are right, I should have noticed. In this case the way to go is to find a place to poll a flag in the C++ code, run the C++ code in a thread (remember to release the GIL in case boost.python doesn't take care of it), let the main thread sleep for half a second and than raise the flag.Blaine

© 2022 - 2024 — McMap. All rights reserved.