Reputation:
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.
Upvotes: 2
Views: 429
Reputation: 16624
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.
Upvotes: 1
Reputation:
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")
Upvotes: 0
Reputation: 783
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);
Upvotes: 0