2

OS: linux
Python version: 3.6

I'm trying to extend a C application with Python runtime. The C application uses pthread and I tried to use multiprocessing forkserver in Python runtime but faced a problem. When I try to kill the program with SIGINT signal (by hitting Ctrl+C in the terminal) the worker processes get killed but the main program hangs.

Here is a toy program that produces the same problem.

#include <Python.h>
#include <pthread.h>

void * thread_start(void *unsed)
{
    PyObject *fs_mod = PyImport_AddModule("fs");
    PyObject *apply_fn = PyObject_GetAttrString(fs_mod, "apply");
    PyObject *job_fn = PyObject_GetAttrString(fs_mod, "job");
    PyObject *job_args = Py_BuildValue("()");
    PyObject_CallFunctionObjArgs(apply_fn, job_fn, job_args, NULL);
    printf("finished\n");
    return NULL;
}

int main(){
    Py_Initialize();
    PyRun_SimpleString(
        "import sys; sys.path.append('...');"
        "sys.argv=['a.out'];"  // prepare a dummy argument to avoid error in forkserver
        "import fs\n"
        "if __name__ == '__main__': fs.init()");

    while(1){
        pthread_t thread;
        pthread_create(&thread, 0, &thread_start, NULL);
        printf("joing\n");
        pthread_join(thread, 0);
    }
}
import multiprocessing as mp

pool = None


def job():
    import time
    print("running..")
    time.sleep(5)

def init():
    global pool
    mp.set_start_method('forkserver')
    pool = mp.Pool(1)

def apply(*args):
    global pool
    return pool.apply(*args)

I don't exactly know how Linux signal works. I tried to catch SIGINT signal in the main python process with signal module, but it seems that main it doesn't get to receive the signal. How would I be able to make this application die gracefully on SIGINT without hanging forever?


By reading ViKiG answer, I realized that I can first catch the KeyboardInterrupt(or SIGINT) exception in the worker processes and send some sentinel value to the main process to notify the exception and shut down the application.

After skimming through the CPython forkserver implementation, I potentially concluded that the author of the library intentionally made the main process ignore the SIGINT. I guess, currently, that the recommended way is to catch the exception in the worker processes, not in the main one.

  • I searched on [python tag and SIGINT to find this answered question](https://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python). – jwdonahue Aug 23 '18 at 06:59
  • Possible duplicate of [How do I capture SIGINT in Python?](https://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python) – jwdonahue Aug 23 '18 at 06:59
  • @jwdonahue I tried signal module but failed to catch the SIGINT signal. That’s why I mentioned that “the main process doesn’t get to receive SIGINT signal” –  Aug 23 '18 at 10:07

3 Answers3

1

Py_Initialize() will install python's own singal handler, call Py_InitializeEx(0) instead:

void Py_InitializeEx(int initsigs)

This function works like Py_Initialize() if initsigs is 1. If initsigs is 0, it skips initialization registration of signal handlers, which might be useful when Python is embedded.

see more on its doc, and cpython source.

georgexsh
  • 15,984
  • 2
  • 37
  • 62
0

I changed job function to handle CTRL+C interrupts:

    def job():
        import time
        try:    
            while True:
                print("running..")
                time.sleep(5)
        except KeyboardInterrupt:
            print 'Exiting job..'

My test program is exiting cleanly after above change.

AFTER EDIT:

I added this to my C program

    #include<signal.h>

    void handler() {printf("Exiting main.."); exit(0);}

Modified main as:

    int main() {
        signal(SIGINT, handler);
ViKiG
  • 764
  • 9
  • 21
  • Thank you for your experiment, but I just realized that I wrote a little wrong example for my case. I'm really sorry but I let me please change the example a bit(The infinite loop should be in the main process, not in the worker process) –  Aug 23 '18 at 11:44
  • Thank you for your comment, but I think that is not exactly how you make Python runtime die gracefully. –  Aug 23 '18 at 12:27
0

It turned out that I don't have to catch the exception in the main process. I solved the problem by catching the KeyboardInterrupt(or SIGINT) exception in the worker processes and send some sentinel value to the main process to notify the exception and shut down the application.

import multiprocessing as mp


pool = None


def job():
    try:
        import time
        print("running..")
        time.sleep(5)
        return True
    except KeyboardInterrupt:
        print("Exiting..")
        return False
...

def apply(*args):
    global pool
    ret = pool.apply(*args)
    if ret:
        return pool.apply(*args)
    else:
        print("Gracefully die")