diff --git a/master.c b/master.c index 75be9a16..6989fa3d 100644 --- a/master.c +++ b/master.c @@ -1563,6 +1563,9 @@ nextlock: else if (WIFEXITED(waitpid_status) && WEXITSTATUS(waitpid_status) == UWSGI_DE_HIJACKED_CODE) { uwsgi_log("...restoring worker %d (pid: %d)...\n", uwsgi.mywid, (int) diedpid); } + else if (WIFEXITED(waitpid_status) && WEXITSTATUS(waitpid_status) == UWSGI_EXCEPTION_CODE) { + uwsgi_log("... monitored exception detected, respawning worker %d (pid: %d)...\n", uwsgi.mywid, (int) diedpid); + } else if (WIFEXITED(waitpid_status) && WEXITSTATUS(waitpid_status) == UWSGI_QUIET_CODE) { // noop } diff --git a/plugins/python/pump_subhandler.c b/plugins/python/pump_subhandler.c index 702dd7e0..e7eadfed 100644 --- a/plugins/python/pump_subhandler.c +++ b/plugins/python/pump_subhandler.c @@ -375,12 +375,6 @@ int uwsgi_response_subhandler_pump(struct wsgi_request *wsgi_req) { return UWSGI_AGAIN; clear: - if (wsgi_req->async_input) { - Py_DECREF((PyObject *)wsgi_req->async_input); - } - if (wsgi_req->async_environ) { - PyDict_Clear(wsgi_req->async_environ); - } Py_XDECREF((PyObject *)wsgi_req->async_placeholder); Py_DECREF((PyObject *)wsgi_req->async_result); diff --git a/plugins/python/python_plugin.c b/plugins/python/python_plugin.c index a90db4c7..006039e5 100644 --- a/plugins/python/python_plugin.c +++ b/plugins/python/python_plugin.c @@ -108,8 +108,6 @@ struct uwsgi_option uwsgi_python_options[] = { {"ini-paste", required_argument, 0, "load a paste.deploy config file containing uwsgi section", uwsgi_opt_ini_paste, NULL, UWSGI_OPT_IMMEDIATE}, {"ini-paste-logged", required_argument, 0, "load a paste.deploy config file containing uwsgi section (load loggers too)", uwsgi_opt_ini_paste, NULL, UWSGI_OPT_IMMEDIATE}, #endif - {"catch-exceptions", no_argument, 0, "report exception has http output (discouraged)", uwsgi_opt_true, &up.catch_exceptions, 0}, - {"ignore-script-name", no_argument, 0, "ignore SCRIPT_NAME", uwsgi_opt_true, &up.ignore_script_name, 0}, {"reload-os-env", no_argument, 0, "force reload of os.environ at each request", uwsgi_opt_true, &up.reload_os_env, 0}, #ifndef UWSGI_PYPY {"no-site", no_argument, 0, "do not import site module", uwsgi_opt_true, &Py_NoSiteFlag, 0}, diff --git a/plugins/python/pyutils.c b/plugins/python/pyutils.c index c935b7ed..b574e7b9 100644 --- a/plugins/python/pyutils.c +++ b/plugins/python/pyutils.c @@ -8,20 +8,103 @@ int manage_python_response(struct wsgi_request *wsgi_req) { return uwsgi_response_subhandler_wsgi(wsgi_req); } +char *uwsgi_python_get_exception_type(PyObject *exc) { + char *class_name = NULL; + if (PyClass_Check(exc)) { + class_name = PyString_AsString( ((PyClassObject*)(exc))->cl_name ); + } + else { + class_name = (char *) ((PyTypeObject*)exc)->tp_name; + } + + if (class_name) { + char *dot = strrchr(class_name, '.'); + if (dot) class_name = dot+1; + + PyObject *module_name = PyObject_GetAttrString(exc, "__module__"); + if (module_name) { + char *mod_name = PyString_AsString(module_name); + if (mod_name && strcmp(mod_name, "exceptions") ) { + char *ret = uwsgi_concat3(mod_name, ".", class_name); + Py_DECREF(module_name); + return ret; + } + Py_DECREF(module_name); + return uwsgi_str(class_name); + } + } + + return NULL; +} + +char *uwsgi_python_get_exception_value(PyObject *value) { + return PyString_AsString( PyObject_Str(value) ); +} + +char *uwsgi_python_get_exception_repr(PyObject *exc, PyObject *value) { + char *exc_type = uwsgi_python_get_exception_type(exc); + char *exc_value = uwsgi_python_get_exception_value(value); + + if (exc_type && exc_value) { + return uwsgi_concat3(exc_type, ": ", exc_value); + } + + return NULL; +} + +int uwsgi_python_manage_exceptions(void) { + PyObject *type = NULL; + PyObject *value = NULL; + PyObject *traceback = NULL; + + char *exc_type = NULL; + char *exc_value = NULL; + char *exc_repr = NULL; + + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + + if (uwsgi.reload_on_exception_type) { + exc_type = uwsgi_python_get_exception_type(type); + } + + if (uwsgi.reload_on_exception_value) { + exc_value = uwsgi_python_get_exception_value(value); + } + + if (uwsgi.reload_on_exception_repr) { + exc_repr = uwsgi_python_get_exception_repr(type, value); + } + + int ret = uwsgi_manage_exception(exc_type, exc_value, exc_repr); + + // free memory allocated for strcmp + if (exc_type) free(exc_type); + if (exc_repr) free(exc_repr); + + PyErr_Restore(type, value, traceback); + + return ret; +} + PyObject *python_call(PyObject *callable, PyObject *args, int catch, struct wsgi_request *wsgi_req) { PyObject *pyret; //uwsgi_log("ready to call %p %p\n", callable, args); - pyret = PyEval_CallObject(callable, args); + pyret = PyEval_CallObject(callable, args); //uwsgi_log("called\n"); if (PyErr_Occurred()) { + + int do_exit = uwsgi_python_manage_exceptions(); + if (PyErr_ExceptionMatches(PyExc_MemoryError)) { uwsgi_log("Memory Error detected !!!\n"); } + // this can be in a spooler or in the master if (uwsgi.mywid > 0) { uwsgi.workers[uwsgi.mywid].exceptions++; @@ -32,6 +115,10 @@ PyObject *python_call(PyObject *callable, PyObject *args, int catch, struct wsgi if (!catch) { PyErr_Print(); } + + if (do_exit) { + exit(UWSGI_EXCEPTION_CODE); + } } #ifdef UWSGI_DEBUG diff --git a/plugins/python/uwsgi_python.h b/plugins/python/uwsgi_python.h index 5107a98b..9d2e34cc 100644 --- a/plugins/python/uwsgi_python.h +++ b/plugins/python/uwsgi_python.h @@ -136,9 +136,6 @@ struct uwsgi_python { char *callable; - int ignore_script_name; - int catch_exceptions; - int *current_recursion_depth; struct _frame **current_frame; diff --git a/plugins/python/web3_subhandler.c b/plugins/python/web3_subhandler.c index 1bde7d5e..487e3df6 100644 --- a/plugins/python/web3_subhandler.c +++ b/plugins/python/web3_subhandler.c @@ -225,12 +225,6 @@ int uwsgi_response_subhandler_web3(struct wsgi_request *wsgi_req) { return UWSGI_AGAIN; clear: - if (wsgi_req->async_input) { - Py_DECREF((PyObject *)wsgi_req->async_input); - } - if (wsgi_req->async_environ) { - PyDict_Clear(wsgi_req->async_environ); - } Py_XDECREF((PyObject *)wsgi_req->async_placeholder); Py_DECREF((PyObject *)wsgi_req->async_result); diff --git a/plugins/python/wsgi_handlers.c b/plugins/python/wsgi_handlers.c index 4df9f36c..7fb0e70f 100644 --- a/plugins/python/wsgi_handlers.c +++ b/plugins/python/wsgi_handlers.c @@ -363,7 +363,7 @@ int uwsgi_request_wsgi(struct wsgi_request *wsgi_req) { if (wsgi_req->appid_len == 0) { - if (!up.ignore_script_name) { + if (!uwsgi.ignore_script_name) { wsgi_req->appid = wsgi_req->script_name; wsgi_req->appid_len = wsgi_req->script_name_len; } @@ -466,7 +466,7 @@ int uwsgi_request_wsgi(struct wsgi_request *wsgi_req) { } - else if (up.catch_exceptions) { + else if (uwsgi.catch_exceptions) { // LOCK THIS PART @@ -501,6 +501,14 @@ int uwsgi_request_wsgi(struct wsgi_request *wsgi_req) { close(tmp_stderr); } + // this object must be freed/cleared always + if (wsgi_req->async_input) { + Py_DECREF((PyObject *)wsgi_req->async_input); + } + if (wsgi_req->async_environ) { + PyDict_Clear(wsgi_req->async_environ); + } + clear: up.reset_ts(wsgi_req, wi); diff --git a/plugins/python/wsgi_subhandler.c b/plugins/python/wsgi_subhandler.c index d007dffa..2ef7792d 100644 --- a/plugins/python/wsgi_subhandler.c +++ b/plugins/python/wsgi_subhandler.c @@ -159,7 +159,7 @@ void *uwsgi_request_subhandler_wsgi(struct wsgi_request *wsgi_req, struct uwsgi_ // call PyTuple_SetItem(wsgi_req->async_args, 0, wsgi_req->async_environ); - return python_call(wsgi_req->async_app, wsgi_req->async_args, up.catch_exceptions, wsgi_req); + return python_call(wsgi_req->async_app, wsgi_req->async_args, uwsgi.catch_exceptions, wsgi_req); } int uwsgi_response_subhandler_wsgi(struct wsgi_request *wsgi_req) { @@ -280,12 +280,6 @@ clear: if (wsgi_req->sendfile_fd != -1) { Py_DECREF((PyObject *)wsgi_req->async_sendfile); } - if (wsgi_req->async_input) { - Py_DECREF((PyObject *)wsgi_req->async_input); - } - if (wsgi_req->async_environ) { - PyDict_Clear(wsgi_req->async_environ); - } Py_XDECREF((PyObject *)wsgi_req->async_placeholder); clear2: Py_DECREF((PyObject *)wsgi_req->async_result); diff --git a/utils.c b/utils.c index b1d1dd55..67c58a3c 100644 --- a/utils.c +++ b/utils.c @@ -4161,3 +4161,46 @@ void uwsgi_write_pidfile(char *pidfile_name) { } fclose(pidfile); } + +int uwsgi_manage_exception(char *type, char *value, char *repr) { + + struct uwsgi_string_list *list = NULL; + + // first manage non fatal case (like signals and alarm).... + + if (uwsgi.reload_on_exception) { + return -1; + } + + if (type) { + list = uwsgi.reload_on_exception_type; + while(list) { + if (!strcmp(list->value, type)) { + return -1; + } + list = list->next; + } + } + + if (value) { + list = uwsgi.reload_on_exception_value; + while(list) { + if (!strcmp(list->value, value)) { + return -1; + } + list = list->next; + } + } + + if (repr) { + list = uwsgi.reload_on_exception_repr; + while(list) { + if (!strcmp(list->value, repr)) { + return -1; + } + list = list->next; + } + } + + return 0; +} diff --git a/uwsgi.c b/uwsgi.c index 948fec73..f1993e1a 100644 --- a/uwsgi.c +++ b/uwsgi.c @@ -258,6 +258,12 @@ static struct uwsgi_option uwsgi_base_options[] = { {"upload-progress", required_argument, 0, "enable creation of .json files in the specified directory during a file upload", uwsgi_opt_set_str, &uwsgi.upload_progress,0}, {"no-default-app", no_argument, 0, "do not fallback to default app", uwsgi_opt_true, &uwsgi.no_default_app, 0}, {"manage-script-name", no_argument, 0, "automatically rewrite SCRIPT_NAME and PATH_INFO", uwsgi_opt_true, &uwsgi.manage_script_name, 0}, + {"ignore-script-name", no_argument, 0, "ignore SCRIPT_NAME", uwsgi_opt_true, &uwsgi.ignore_script_name, 0}, + {"catch-exceptions", no_argument, 0, "report exception has http output (discouraged)", uwsgi_opt_true, &uwsgi.catch_exceptions, 0}, + {"reload-on-exception", no_argument, 0, "reload a worker when an exception is raised", uwsgi_opt_true, &uwsgi.reload_on_exception, 0}, + {"reload-on-exception-type", no_argument, 0, "reload a worker when a specific exception type is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_type, 0}, + {"reload-on-exception-value", no_argument, 0, "reload a worker when a specific exception value is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_value, 0}, + {"reload-on-exception-repr", no_argument, 0, "reload a worker when a specific exception type+value (language-specific) is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_repr, 0}, #ifdef UWSGI_UDP {"udp", required_argument, 0, "run the udp server on the specified address", uwsgi_opt_set_str, &uwsgi.udp_socket, UWSGI_OPT_MASTER}, #endif diff --git a/uwsgi.h b/uwsgi.h index ef85688c..a1ebffa8 100644 --- a/uwsgi.h +++ b/uwsgi.h @@ -548,6 +548,7 @@ struct uwsgi_opt { #define UWSGI_EXILE_CODE 26 #define UWSGI_FAILED_APP_CODE 22 #define UWSGI_DE_HIJACKED_CODE 173 +#define UWSGI_EXCEPTION_CODE 5 #define UWSGI_QUIET_CODE 29 #define MAX_VARS 64 @@ -1205,6 +1206,13 @@ struct uwsgi_server { int ignore_script_name; int manage_script_name; + int reload_on_exception; + int catch_exceptions; + struct uwsgi_string_list *reload_on_exception_type; + struct uwsgi_string_list *reload_on_exception_value; + struct uwsgi_string_list *reload_on_exception_repr; + + int no_default_app; // exit if no-app is loaded int need_app; @@ -1448,7 +1456,6 @@ struct uwsgi_server { char *chdir; char *chdir2; - int catch_exceptions; int vacuum; int no_server; @@ -2754,6 +2761,7 @@ void uwsgi_setup_post_buffering(void); struct uwsgi_lock_item *uwsgi_lock_ipcsem_init(char *); void uwsgi_write_pidfile(char *); +int uwsgi_manage_exception(char *, char *, char *); #ifdef UWSGI_AS_SHARED_LIBRARY int uwsgi_init(int, char **, char **);