From a9aa9e76db2766a681350c09947df848898531f6 Mon Sep 17 00:00:00 2001 From: Gourav Date: Wed, 26 Jun 2024 11:14:50 +0530 Subject: python: Support application factories Adds support for the app factory pattern to the Python language module. A factory is a callable that returns a WSGI or ASGI application object. Unit does not support passing arguments to factories. Setting the `factory` option to `true` instructs Unit to treat the configured `callable` as a factory. For example: "my-app": { "type": "python", "path": "/srv/www/", "module": "hello", "callable": "create_app", "factory": true } This is similar to other WSGI / ASGI servers. E.g., $ uvicorn --factory hello:create_app $ gunicorn 'hello:create_app()' The factory setting defaults to false. Closes: https://github.com/nginx/unit/issues/1106 Link: [ Commit message - Dan / Minor code tweaks - Andrew ] Signed-off-by: Andrew Clayton --- src/python/nxt_python.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'src/python/nxt_python.c') diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index 7c059649..aa0f65b1 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -403,11 +403,13 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, char *callable, *module_name; PyObject *module, *obj; nxt_str_t str; + nxt_bool_t is_factory = 0; nxt_conf_value_t *value; static nxt_str_t module_str = nxt_string("module"); static nxt_str_t callable_str = nxt_string("callable"); static nxt_str_t prefix_str = nxt_string("prefix"); + static nxt_str_t factory_flag_str = nxt_string("factory"); module = obj = NULL; @@ -449,7 +451,30 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, goto fail; } - if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + value = nxt_conf_get_object_member(conf, &factory_flag_str, NULL); + if (value != NULL) { + is_factory = nxt_conf_get_boolean(value); + } + + if (is_factory) { + if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + nxt_alert(task, + "factory \"%s\" in module \"%s\" " + "can not be called to fetch callable", + callable, module_name); + goto fail; + } + + obj = PyObject_CallObject(obj, NULL); + if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + nxt_alert(task, + "factory \"%s\" in module \"%s\" " + "did not return callable object", + callable, module_name); + goto fail; + } + + } else if (nxt_slow_path(PyCallable_Check(obj) == 0)) { nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", callable, module_name); goto fail; -- cgit From ff6d504530ad2c126fc264744faa9e62bcc43fb9 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Sat, 22 Jun 2024 00:20:00 +0100 Subject: python: Constify some local static variables These somehow got missed in my previous constification patches... Signed-off-by: Andrew Clayton --- src/python/nxt_python.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/python/nxt_python.c') diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index aa0f65b1..7bbf3d49 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -406,10 +406,10 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, nxt_bool_t is_factory = 0; nxt_conf_value_t *value; - static nxt_str_t module_str = nxt_string("module"); - static nxt_str_t callable_str = nxt_string("callable"); - static nxt_str_t prefix_str = nxt_string("prefix"); - static nxt_str_t factory_flag_str = nxt_string("factory"); + static const nxt_str_t module_str = nxt_string("module"); + static const nxt_str_t callable_str = nxt_string("callable"); + static const nxt_str_t prefix_str = nxt_string("prefix"); + static const nxt_str_t factory_flag_str = nxt_string("factory"); module = obj = NULL; -- cgit From 50b1aca3b8318c58f7073fe11911f1f0d52c651d Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Thu, 12 Sep 2024 16:48:10 +0100 Subject: python: Don't decrement a reference to a borrowed object On some Python 3.11 systems, 3.11.9 & 3.11.10, we were seeing a crash triggered by Py_Finalize() in nxt_python_atexit() when running one of our pytests, namely test/test_python_factory.py::test_python_factory_invalid_callable_value 2024/09/12 15:07:29 [alert] 5452#5452 factory "wsgi_invalid_callable" in module "wsgi" can not be called to fetch callable Fatal Python error: none_dealloc: deallocating None: bug likely caused by a refcount error in a C extension Python runtime state: finalizing (tstate=0x00007f560b88a718) Current thread 0x00007f560bde7ad0 (most recent call first): 2024/09/12 15:07:29 [alert] 5451#5451 app process 5452 exited on signal 6 (core dumped) This was due to obj = PyDict_GetItemString(PyModule_GetDict(module), callable); in nxt_python_set_target() which returns a *borrowed* reference, then due to the test meaning this is a `None` object we `goto fail` and call Py_DECREF(obj); which then causes `Py_Finalize()` to blow up. The simple fix is to just increment its reference count before the `goto fail`. Note: This problem only showed up under (the various versions of Python we test on); 3.11.9 & 3.11.10. It doesn't show up under; 3.6, 3.7, 3.9, 3.10, 3.12 Cc: Konstantin Pavlov Closes: https://github.com/nginx/unit/issues/1413 Fixes: a9aa9e76d ("python: Support application factories") Signed-off-by: Andrew Clayton --- src/python/nxt_python.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/python/nxt_python.c') diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index 7bbf3d49..143d8d5d 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -462,6 +462,7 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, "factory \"%s\" in module \"%s\" " "can not be called to fetch callable", callable, module_name); + Py_INCREF(obj); /* borrowed reference */ goto fail; } -- cgit