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.
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")