Search code examples
pythoncswigabort

How to abort Python script calling C function using Swig?


I have a problem trying to abort a Python script, for example:

#!/usr/bin/python

import hello

hello.draw()

exit()

where draw() is a C function. In this function there is a loop to print some text. I tried CTRL-C to break out of it but it doesn't work. Important thing is that C code cannot be modified!

Any ideas?


Solution

  • I put together an example of how you might do this. It lets to augment the function and escape from it without needing to modify it. For example, given the function foo (which was inline in test.h for my testing):

    void foo() {
      while(1); // never returns
    }
    

    You can play a game with signals and siglongjmp. It should be noted however that you need to be very careful to ensure you only call async safe functions from within foo, or alternatively mask the signals before and unmask them after any unsafe calls.

    We can then wrap the function and use %exception to inject some extra code to allow the signal to backout of the function:

    %module test
    
    %{
    #include <setjmp.h>
    #include <signal.h>
    
    static sigjmp_buf timeout;
    
    static void backout(int sig) {
      siglongjmp(timeout, sig);
    }
    
    #include "test.h"
    %}
    
    %include <exception.i>
    
    %exception {
      if (!sigsetjmp(timeout, 1)) {
        signal(SIGALRM,backout); // Check return?
        $action
      }
      else {
        // raise a Python exception
        SWIG_exception(SWIG_RuntimeError, "Timeout in $decl");
      }
    }
    
    void foo();
    
    %exception;
    

    Which allows us to write:

    import signal
    import test
    
    signal.alarm(5)
    test.foo()
    

    Which works as hoped, given the caveats about async-safe functions. I used SIGALRM here because it allowed me to keep it all self-contained within one Python file. You could use another thread/process and other signals if you preferred.

    If you change SIGALRM in my example to SIGINT (and don't raise a SIGALRM either obviously) it'll work with Ctrl+C - the reason it doesn't is because of how the default Python signal handlers work. The default handler Python uses simply sets a flag and processes the signal after the underlying call has returned control to Python, which neatly side-steps the whole async-safety issue.