diff --git a/src/python.c b/src/python.c
index f5b8b93de3369921410ec1de4fe296dd9f96e5ba..912c18ae227bbd3d8379130ce1f5942d7af80eea 100644 (file)
--- a/src/python.c
+++ b/src/python.c
@@ -219,6 +219,9 @@ static char reg_shutdown_doc[] = "register_shutdown(callback[, data][, name]) ->
" data if it was supplied.";
+static pthread_t main_thread;
+static PyOS_sighandler_t python_sigint_handler;
+static _Bool do_interactive = 0;
static int do_interactive = 0;
/* This is our global thread state. Python saves some stuff in thread-local
return 0;
}
-static void cpy_int_handler(int sig) {
- return;
-}
-
static void *cpy_interactive(void *data) {
- sigset_t sigset;
- struct sigaction old;
+ PyOS_sighandler_t cur_sig;
/* Signal handler in a plugin? Bad stuff, but the best way to
* handle it I guess. In an interactive session people will
* mess. Chances are, this isn't what the user wanted to do.
*
* So this is the plan:
- * 1. Block SIGINT in the main thread.
- * 2. Install our own signal handler that does nothing.
- * 3. Unblock SIGINT in the interactive thread.
+ * 1. Restore Python's own signal handler
+ * 2. Tell Python we just forked so it will accept this thread
+ * as the main one. No version of Python will ever handle
+ * interrupts anywhere but in the main thread.
+ * 3. After the interactive loop is done, restore collectd's
+ * SIGINT handler.
+ * 4. Raise SIGINT for a clean shutdown. The signal is sent to
+ * the main thread to ensure it wakes up the main interval
+ * sleep so that collectd shuts down immediately not in 10
+ * seconds.
*
* This will make sure that SIGINT won't kill collectd but
- * still interrupt syscalls like sleep and pause.
- * It does not raise a KeyboardInterrupt exception because so
- * far nobody managed to figure out how to do that. */
- struct sigaction sig_int_action = {
- .sa_handler = cpy_int_handler
- };
- sigaction (SIGINT, &sig_int_action, &old);
-
- sigemptyset(&sigset);
- sigaddset(&sigset, SIGINT);
- pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+ * still interrupt syscalls like sleep and pause. */
+
PyEval_AcquireThread(state);
if (PyImport_ImportModule("readline") == NULL) {
/* This interactive session will suck. */
cpy_log_exception("interactive session init");
- }
+ }
+ cur_sig = PyOS_setsig(SIGINT, python_sigint_handler);
+ /* We totally forked just now. Everyone saw that, right? */
+ PyOS_AfterFork();
PyRun_InteractiveLoop(stdin, "<stdin>");
+ PyOS_setsig(SIGINT, cur_sig);
PyErr_Print();
PyEval_ReleaseThread(state);
NOTICE("python: Interactive interpreter exited, stopping collectd ...");
- /* Restore the original collectd SIGINT handler and raise SIGINT.
- * The main thread still has SIGINT blocked and there's nothing we
- * can do about that so this thread will handle it. But that's not
- * important, except that it won't interrupt the main loop and so
- * it might take a few seconds before collectd really shuts down. */
- sigaction (SIGINT, &old, NULL);
- raise(SIGINT);
- pause();
+ pthread_kill(main_thread, SIGINT);
return NULL;
}
static int cpy_init(void) {
PyObject *ret;
static pthread_t thread;
- sigset_t sigset;
if (!Py_IsInitialized()) {
WARNING("python: Plugin loaded but not configured.");
else
Py_DECREF(ret);
}
- sigemptyset(&sigset);
- sigaddset(&sigset, SIGINT);
- pthread_sigmask(SIG_BLOCK, &sigset, NULL);
state = PyEval_SaveThread();
+ main_thread = pthread_self();
if (do_interactive) {
if (plugin_thread_create(&thread, NULL, cpy_interactive, NULL)) {
ERROR("python: Error creating thread for interactive interpreter.");
#endif
static int cpy_init_python(void) {
+ PyOS_sighandler_t cur_sig;
PyObject *sys;
PyObject *module;
char *argv = "";
#endif
+ /* Chances are the current signal handler is already SIG_DFL, but let's make sure. */
+ cur_sig = PyOS_setsig(SIGINT, SIG_DFL);
Py_Initialize();
+ python_sigint_handler = PyOS_setsig(SIGINT, cur_sig);
PyType_Ready(&ConfigType);
PyType_Ready(&PluginDataType);
static int cpy_config(oconfig_item_t *ci) {
PyObject *tb;
+ int status = 0;
/* Ok in theory we shouldn't do initialization at this point
* but we have to. In order to give python scripts a chance
oconfig_item_t *item = ci->children + i;
if (strcasecmp(item->key, "Interactive") == 0) {
- if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+ if (cf_util_get_boolean(item, &do_interactive) != 0) {
+ status = 1;
continue;
- do_interactive = item->values[0].value.boolean;
+ }
} else if (strcasecmp(item->key, "Encoding") == 0) {
- if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_STRING)
+ char *encoding = NULL;
+ if (cf_util_get_string(item, &encoding) != 0) {
+ status = 1;
continue;
+ }
#ifdef IS_PY3K
- NOTICE("python: \"Encoding\" was used in the config file but Python3 was used, which does not support changing encodings. Ignoring this.");
+ ERROR("python: \"Encoding\" was used in the config file but Python3 was used, which does not support changing encodings");
+ status = 1;
+ sfree(encoding);
+ continue;
#else
/* Why is this even necessary? And undocumented? */
- if (PyUnicode_SetDefaultEncoding(item->values[0].value.string))
+ if (PyUnicode_SetDefaultEncoding(encoding)) {
cpy_log_exception("setting default encoding");
+ status = 1;
+ }
#endif
+ sfree(encoding);
} else if (strcasecmp(item->key, "LogTraces") == 0) {
- if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+ _Bool log_traces;
+ if (cf_util_get_boolean(item, &log_traces) != 0) {
+ status = 1;
continue;
- if (!item->values[0].value.boolean) {
+ }
+ if (!log_traces) {
Py_XDECREF(cpy_format_exception);
cpy_format_exception = NULL;
continue;
tb = PyImport_ImportModule("traceback"); /* New reference. */
if (tb == NULL) {
cpy_log_exception("python initialization");
+ status = 1;
continue;
}
cpy_format_exception = PyObject_GetAttrString(tb, "format_exception"); /* New reference. */
Py_DECREF(tb);
- if (cpy_format_exception == NULL)
+ if (cpy_format_exception == NULL) {
cpy_log_exception("python initialization");
+ status = 1;
+ }
} else if (strcasecmp(item->key, "ModulePath") == 0) {
char *dir = NULL;
PyObject *dir_object;
- if (cf_util_get_string(item, &dir) != 0)
+ if (cf_util_get_string(item, &dir) != 0) {
+ status = 1;
continue;
+ }
dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */
if (dir_object == NULL) {
ERROR("python plugin: Unable to convert \"%s\" to "
"a python object.", dir);
free(dir);
cpy_log_exception("python initialization");
+ status = 1;
continue;
}
if (PyList_Insert(sys_path, 0, dir_object) != 0) {
ERROR("python plugin: Unable to prepend \"%s\" to "
"python module path.", dir);
cpy_log_exception("python initialization");
+ status = 1;
}
Py_DECREF(dir_object);
free(dir);
char *module_name = NULL;
PyObject *module;
- if (cf_util_get_string(item, &module_name) != 0)
+ if (cf_util_get_string(item, &module_name) != 0) {
+ status = 1;
continue;
+ }
module = PyImport_ImportModule(module_name); /* New reference. */
if (module == NULL) {
ERROR("python plugin: Error importing module \"%s\".", module_name);
cpy_log_exception("importing module");
+ status = 1;
}
free(module_name);
Py_XDECREF(module);
cpy_callback_t *c;
PyObject *ret;
- if (cf_util_get_string(item, &name) != 0)
+ if (cf_util_get_string(item, &name) != 0) {
+ status = 1;
continue;
+ }
for (c = cpy_config_callbacks; c; c = c->next) {
if (strcasecmp(c->name + 7, name) == 0)
break;
else
ret = PyObject_CallFunction(c->callback, "NO",
cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
- if (ret == NULL)
+ if (ret == NULL) {
cpy_log_exception("loading module");
- else
+ status = 1;
+ } else
Py_DECREF(ret);
} else {
- WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
+ ERROR("python plugin: Unknown config key \"%s\".", item->key);
+ status = 1;
}
}
- return 0;
+ return (status);
}
void module_register(void) {