Skip to content Skip to sidebar Skip to footer

How Do I Catch An Interrupt Signal In Python When Inside A Blocking Boost C++ Method?

I have a toolset which has been written in C++, and given Boost bindings for Python. Initially, this code was all C++, and I caught a CTRL+C interrupt with: signal( SIGINT, signalC

Solution 1:

Your python signal handler is not called because python defers execution of signal handlers until after the next bytecode instruction is to be executed - see the library documentation for signal, section 18.8.1.1:

A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction). This has consequences:

  • It makes little sense to catch synchronous errors like SIGFPE or SIGSEGV that are caused by an invalid operation in C code. Python will return from the signal handler to the C code, which is likely to raise the same signal again, causing Python to apparently hang. From Python 3.3 onwards, you can use the faulthandler module to report on synchronous errors.
  • A long-running calculation implemented purely in C (such as regular expression matching on a large body of text) may run uninterrupted for an arbitrary amount of time, regardless of any signals received. The Python signal handlers will be called when the calculation finishes.

The reason for this is that a signal can arrive at any time, potentially half way through the execution of a python instruction. It would not be safe for the VM to begin executing the signal handler, because the VM is in an unknown state. Therefore, the actual signal handler installed by python merely sets a flag telling the VM to call the signal handler after the current instruction is complete.

If the signal arrives during execution of your C++ function, then the signal handler sets the flag and returns back to your C++ function.

If the main purpose of the signal handler is to allow the C++ function to be interrupted, then I suggest you dispense with the Python signal handler and install a C++ signal handler that sets a flag which triggers an early exit in your C++ code (presumably returning a value that indicates it was interrupted).

That approach would allow you to use the same code regardless of whether you are calling your code from python, C++ or perhaps another binding.

Solution 2:

I have a solution that works for this, although it'd be cleaner if I could get the signal caught in Python rather than C++.

One thing I didn't mention before is that MyObject is a singleton, so I'm getting it with MyObject.getObject()

In Python, I've got:

def signalHandler( signum ) :
    if signum == signal.SIGINT :
        MyObject.getObject().stop()

def main() :
    signal.signal( signal.SIGINT, handle_interrupt )

    myObject = MyObject.getObject()
    myObject.addSignalHandler( signal.SIGINT, signalHandler )
    myObject.start()

In my C++ code, in an area that shouldn't know anything about Python, I have:

classMyObject
{
    public :
        voidaddSignalHandler( int signum, void (*handler)( int, void* ), void *data = nullptr );
        voidcallSignalHandler( int signum );
    private :
        std::map<int, std::pair<void (*)( int, void* ), void*> > m_signalHandlers;
}

voidsignalCallbackHandler( int signum ){
    MyObject::getObject()->callSignalHandler( signum );
}

voidMyObject::addSignalHandler( int signum, void (*handler)( int, void* ), void *data ){
    m_signalHandlers.insert( std::pair<int, std::pair<void (*)( int, void* ), void *> >( signum, std::make_pair( handler, data ) ) );
    signal( signum, signalCallbackHandler );
}

voidMyObject::callSignalHandler( int signum ){
    std::map<int, std::pair<void (*)( int, void* ), void*> >::iterator handler = m_signalHandlers.find( signum );
    if( handler != m_signalHandlers.end() )
    {
        handler->second.first( signum, handler->second.second );
    }
}

And then in my Python bindings:

void signalHandlerWrapper( int signum, void *data )
{
    if( nullptr == data )
    {
        return;
    }

    PyObject *func = (PyObject*)( data );
    if( PyFunction_Check( func ) )
    {
        PyObject_CallFunction( func, "i", signum );
    }
}

void addSignalHandlerWrapper( MyObject *o, int signum, PyObject *func )
{
    Py_INCREF( func );if( PyFunction_Check( func ) )
    {
        o->addSignalHandler( signum, &signalHandlerWrapper, func );
    }
}

What I don't have, which I should add, is something in addSignalHandlerWrapper() that will check if there was already something fo that signal number, and if so, get it and decrement the reference before adding the new one. I haven't done this yet, as this functionality is only used for ending the program, but for completeness, it should be put in place.

Anyway, as I said at the start, this is more involved than it could be. It also only works as I have a singleton that can keep track of the function pointers.

Post a Comment for "How Do I Catch An Interrupt Signal In Python When Inside A Blocking Boost C++ Method?"