From bcde41f310eab82abaeb98369bfca47ea61b5be3 Mon Sep 17 00:00:00 2001 From: Lalufu Date: Sat, 26 Oct 2024 11:13:54 +0200 Subject: [PATCH 1/4] Fix reload-on-touch/change (#2689) * Fix reload-on-touch/change As part of a fix for #2656, commit 8f1d0e5 introduced a measure to wait for processes to finish work while reloading. The code to do this affects other logic flows was well, breaking the ability to reload uwsgi when a config file changes. This moves the code around a bit so #2656 stays fixed, but reload-on-touch/change works again. Fixes #2681. * Apply suggestions from code review --------- Co-authored-by: Riccardo Magliocchetti --- core/master_utils.c | 6 ------ core/uwsgi.c | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/master_utils.c b/core/master_utils.c index 61bc75f20..27753d561 100644 --- a/core/master_utils.c +++ b/core/master_utils.c @@ -54,12 +54,6 @@ void uwsgi_destroy_processes() { uwsgi_detach_daemons(); - for (i = 1; i <= uwsgi.numproc; i++) { - if (uwsgi.workers[i].pid > 0) { - waitpid(uwsgi.workers[i].pid, &waitpid_status, 0); - } - } - for (i = 0; i < ushared->gateways_cnt; i++) { if (ushared->gateways[i].pid > 0) { kill(ushared->gateways[i].pid, SIGKILL); diff --git a/core/uwsgi.c b/core/uwsgi.c index 74477e90d..9dcba6626 100644 --- a/core/uwsgi.c +++ b/core/uwsgi.c @@ -1319,6 +1319,8 @@ void kill_them_all(int signum) { // gracefully destroy void gracefully_kill_them_all(int signum) { + int waitpid_status; + if (uwsgi_instance_is_dying) return; uwsgi.status.gracefully_destroying = 1; @@ -1341,6 +1343,12 @@ void gracefully_kill_them_all(int signum) { } } + for (i = 1; i <= uwsgi.numproc; i++) { + if (uwsgi.workers[i].pid > 0) { + waitpid(uwsgi.workers[i].pid, &waitpid_status, 0); + } + } + uwsgi_destroy_processes(); } From 5faa6ad6cc733b62b7644e369b6011d4829d89f7 Mon Sep 17 00:00:00 2001 From: niol Date: Sat, 26 Oct 2024 11:35:29 +0200 Subject: [PATCH 2/4] Integration tests : more basic tests (#2684) * integration-tests: better handling of failures * integration-tests: use notfound as basic request plugin * allow --need-app=0 on command line * integration tests: rename plugins to test envvar to avoid conflict with uwsgi * integration tests: add basic test for jvm jwsgi * integration tests: add basic test for psgi * integration tests: add basic test for cgi * integration tests: add basic test for Ruby rack * integration tests: fix failure when run from another dir * fix test_classic_mountpoints with py3 (Fixes #2554) * integration tests: include manage_script_name tests * integration tests: factor test GET body code --- .github/workflows/test.yml | 3 +- buildconf/integration-tests.ini | 2 +- core/uwsgi.c | 2 +- t/cgi/hello.cgi | 6 + t/php/config.ini | 2 +- .../manage_script_name_test.ini | 4 - .../test_manage_script_name.py | 59 -------- t/python/manage_script_name/useless_app.py | 2 +- t/rack/app.ru | 9 ++ t/runner | 142 ++++++++++++++++-- t/static/config.ini | 2 +- 11 files changed, 150 insertions(+), 83 deletions(-) create mode 100755 t/cgi/hello.cgi delete mode 100644 t/python/manage_script_name/test_manage_script_name.py create mode 100644 t/rack/app.ru diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20541b77c..83e476188 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,8 @@ jobs: sudo apt install --no-install-recommends -qqyf \ libpcre2-dev libjansson-dev libcap2-dev \ php-dev libphp-embed libargon2-dev libsodium-dev \ - pypy3 + pypy3 default-jdk-headless libperl-dev \ + ruby-dev ruby-rack - uses: actions/checkout@v4 - name: Set env run: echo "PROFILE=integration-tests" >> $GITHUB_ENV diff --git a/buildconf/integration-tests.ini b/buildconf/integration-tests.ini index 55f4188ca..cf92e64a0 100644 --- a/buildconf/integration-tests.ini +++ b/buildconf/integration-tests.ini @@ -1,4 +1,4 @@ [uwsgi] inherit = base main_plugin = -plugins = python,php,pypy +plugins = notfound,python,php,pypy,jvm,jwsgi,psgi,cgi,rack diff --git a/core/uwsgi.c b/core/uwsgi.c index 9dcba6626..b2c3018d4 100644 --- a/core/uwsgi.c +++ b/core/uwsgi.c @@ -208,7 +208,7 @@ static struct uwsgi_option uwsgi_base_options[] = { {"procname-master", required_argument, 0, "set master process name", uwsgi_opt_set_str, &uwsgi.procname_master, UWSGI_OPT_PROCNAME}, {"single-interpreter", no_argument, 'i', "do not use multiple interpreters (where available)", uwsgi_opt_true, &uwsgi.single_interpreter, 0}, - {"need-app", no_argument, 0, "exit if no app can be loaded", uwsgi_opt_true, &uwsgi.need_app, 0}, + {"need-app", optional_argument, 0, "exit if no app can be loaded", uwsgi_opt_true, &uwsgi.need_app, 0}, {"master", no_argument, 'M', "enable master process", uwsgi_opt_true, &uwsgi.master_process, 0}, {"honour-stdin", no_argument, 0, "do not remap stdin to /dev/null", uwsgi_opt_true, &uwsgi.honour_stdin, 0}, {"emperor", required_argument, 0, "run the Emperor", uwsgi_opt_add_string_list, &uwsgi.emperor, 0}, diff --git a/t/cgi/hello.cgi b/t/cgi/hello.cgi new file mode 100755 index 000000000..b3e56cfcc --- /dev/null +++ b/t/cgi/hello.cgi @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Content-Type: text/plain" +echo +echo "Hello world!" +echo "PATH_INFO=${PATH_INFO}" diff --git a/t/php/config.ini b/t/php/config.ini index a94e3abd5..b1bea5d48 100644 --- a/t/php/config.ini +++ b/t/php/config.ini @@ -10,4 +10,4 @@ cache2 = name=session,items=1000,store=/tmp/uwsgi-session-cache,bitmap=1 php-set = session.save_handler=uwsgi php-set = session.save_path=session -php-docroot = t/php/ +php-docroot = %d diff --git a/t/python/manage_script_name/manage_script_name_test.ini b/t/python/manage_script_name/manage_script_name_test.ini index f2287768f..3563486fc 100644 --- a/t/python/manage_script_name/manage_script_name_test.ini +++ b/t/python/manage_script_name/manage_script_name_test.ini @@ -1,8 +1,4 @@ [uwsgi] -http-socket = :8080 - -master = 1 - ; Three apps on three mountpoints wsgi-file = %d/useless_app.py diff --git a/t/python/manage_script_name/test_manage_script_name.py b/t/python/manage_script_name/test_manage_script_name.py deleted file mode 100644 index 0908b0fc3..000000000 --- a/t/python/manage_script_name/test_manage_script_name.py +++ /dev/null @@ -1,59 +0,0 @@ -#! /usr/bin/env python3 -# coding = utf-8 -# author = Adriano Di Luzio - -# I require requests! - -""" -First run: - $ ./uwsgi t/python/manage_script_name/manage_script_name_test.ini - -Then run me! -""" - -import unittest -import requests - -HOST = "http://127.0.0.1:8080" - - -class ManageScriptNameTest(unittest.TestCase): - - def test_classic_mountpoints(self): - mps = { - "/foo", - "/foobis/", - "/footris/" - } - - for mp in mps: - # Requests to /foo should kick-in the managed script name. - r = requests.get(HOST + mp) - self.assertEqual(r.text, mp) - - ends = mp.endswith("/") - - # And equally requests to /foo/ - r = requests.get( - HOST + mp + "/") if not ends else requests.get(HOST + mp[:-1]) - self.assertEqual(r.text, mp) - - # Or correct requests (/foo/resource) - r = requests.get( - HOST + mp + "/" + "resource") if not ends else requests.get(HOST + mp + "resource") - self.assertEqual(r.text, mp) - - def test_intriguing_mountpoints(self): - mps = { - "/fooanything", - "/foobisis/", - "/foofighters", - } - - for mp in mps: - r = requests.get(HOST + mp) - self.assertEqual(r.text, "") - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/t/python/manage_script_name/useless_app.py b/t/python/manage_script_name/useless_app.py index b19c08104..115f4dbc9 100644 --- a/t/python/manage_script_name/useless_app.py +++ b/t/python/manage_script_name/useless_app.py @@ -1,3 +1,3 @@ def application(env, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) - return env['SCRIPT_NAME'] + return [env['SCRIPT_NAME'].encode('ascii'), ] diff --git a/t/rack/app.ru b/t/rack/app.ru new file mode 100644 index 000000000..6a4323e18 --- /dev/null +++ b/t/rack/app.ru @@ -0,0 +1,9 @@ +class App + + def call(environ) + [200, {'Content-Type' => 'text/html'}, ['Hello']] + end + +end + +run App.new diff --git a/t/runner b/t/runner index 42d639334..b59966b5d 100755 --- a/t/runner +++ b/t/runner @@ -21,7 +21,7 @@ import unittest TESTS_DIR = os.path.dirname(__file__) UWSGI_BINARY = os.getenv("UWSGI_BINARY", os.path.join(TESTS_DIR, "..", "uwsgi")) -UWSGI_PLUGINS = os.getenv("UWSGI_PLUGINS", "all").split(" ") +UWSGI_PLUGINS = os.getenv("UWSGI_PLUGINS_TEST", "all").split(" ") UWSGI_ADDR = "127.0.0.1" UWSGI_PORT = 8000 UWSGI_HTTP = f"{UWSGI_ADDR}:{UWSGI_PORT}" @@ -83,19 +83,23 @@ class UwsgiTest(unittest.TestCase): result = self._outcome.result ok = not (result.errors + result.failures) - self.testserver.send_signal(signal.SIGTERM) - if not ok: - print(self.testserver.stdout.read(), file=sys.stderr) + if hasattr(self, "testserver"): + self.testserver.send_signal(signal.SIGTERM) + if not ok: + print(self.testserver.stdout.read(), file=sys.stderr) - self.testserver.wait() - self.testserver.stdout.close() + self.testserver.wait() + self.testserver.stdout.close() + + def assert_GET_body(self, url_path, body_expected): + with requests.get(f"http://{UWSGI_HTTP}{url_path}") as r: + self.assertEqual(r.text, body_expected) - @unittest.skipUnless(*plugins_available(["python"])) def test_static_expires(self): self.start_listen_server( [ "--plugin", - "python", # provide a request plugin + "notfound", os.path.join(TESTS_DIR, "static", "config.ini"), ] ) @@ -103,6 +107,44 @@ class UwsgiTest(unittest.TestCase): with requests.get(f"http://{UWSGI_HTTP}/foobar/config.ini") as r: self.assertTrue("Expires" in r.headers) + @unittest.skipUnless(*plugins_available(["python"])) + def test_mountpoints(self): + self.start_listen_server( + [ + "--plugin", + "python", + os.path.join( + TESTS_DIR, + "python", + "manage_script_name", + "manage_script_name_test.ini", + ), + ] + ) + + mps = {"/foo", "/foobis/", "/footris/"} + + for mp in mps: + # Requests to /foo should kick-in the managed script name. + self.assert_GET_body(mp, mp) + + ends = mp.endswith("/") + + # And equally requests to /foo/ + self.assert_GET_body(f"{mp}/" if not ends else f"{mp}"[:-1], mp) + + # Or correct requests (/foo/resource) + self.assert_GET_body(f"{mp}/resource" if not ends else f"{mp}resource", mp) + + mps = { + "/fooanything", + "/foobisis/", + "/foofighters", + } + + for mp in mps: + self.assert_GET_body(mp, "") + @unittest.skipUnless(*plugins_available(["python"])) def test_python3_helloworld(self): self.start_listen_server( @@ -114,8 +156,7 @@ class UwsgiTest(unittest.TestCase): ] ) - with requests.get(f"http://{UWSGI_HTTP}/") as r: - self.assertEqual(r.text, "Hello World") + self.assert_GET_body("/", "Hello World") @unittest.skipUnless(*plugins_available(["pypy"])) def test_pypy3_helloworld(self): @@ -125,8 +166,7 @@ class UwsgiTest(unittest.TestCase): ] ) - with requests.get(f"http://{UWSGI_HTTP}/") as r: - self.assertEqual(r.text, "Hello World") + self.assert_GET_body("/", "Hello World") @unittest.skipUnless(*plugins_available(["php"])) def test_php_session(self): @@ -136,8 +176,82 @@ class UwsgiTest(unittest.TestCase): ] ) - with requests.get(f"http://{UWSGI_HTTP}/test.php") as r: - self.assertEqual(r.text, "PASS\n") + self.assert_GET_body("/test.php", "PASS\n") + + @unittest.skipUnless(*plugins_available(["jvm"])) + def test_jvm_hellworld(self): + classpath = ":".join( + [ + "/usr/share/java/uwsgi.jar", + os.path.join(TESTS_DIR, "java"), + os.path.join(TESTS_DIR, "..", "plugins", "jvm"), + ] + ) + + subprocess.call( + [ + "javac", + "-classpath", + classpath, + os.path.join(TESTS_DIR, "java", "rpc.java"), + ] + ) + + self.start_listen_server( + [ + "--need-app=0", + "--plugins", + "0:jvm,jwsgi", + "--jvm-classpath", + classpath, + "--jwsgi", + "rpc:application", + ] + ) + + self.assert_GET_body("/", "

null

") + + @unittest.skipUnless(*plugins_available(["psgi"])) + def test_psgi_helloworld(self): + self.start_listen_server( + [ + "--plugins", + "psgi", + "--psgi", + os.path.join(TESTS_DIR, "perl", "test_hello.psgi"), + ] + ) + + self.assert_GET_body("/", "Hello, world!") + + @unittest.skipUnless(*plugins_available(["cgi"])) + def test_cgi_helloworld(self): + self.start_listen_server( + [ + "--need-app=0", + "--plugins", + "0:cgi", + "--cgi", + os.path.join(TESTS_DIR, "cgi", "hello.cgi"), + ] + ) + + self.assert_GET_body( + "/foobar/say_hello", "Hello world!\nPATH_INFO=/foobar/say_hello\n" + ) + + @unittest.skipUnless(*plugins_available(["rack"])) + def test_rack_helloworld(self): + self.start_listen_server( + [ + "--plugins", + "0:rack", + "--rack", + os.path.join(TESTS_DIR, "rack", "app.ru"), + ] + ) + + self.assert_GET_body("/", "Hello") if __name__ == "__main__": diff --git a/t/static/config.ini b/t/static/config.ini index e0e23c55e..b6bedc0ed 100644 --- a/t/static/config.ini +++ b/t/static/config.ini @@ -1,4 +1,4 @@ [uwsgi] need-app = False -static-map = /foobar=t/static +static-map = /foobar=%d static-expires-uri = ^/foobar/ 315360000 From a45de8ce2f8937a6f7f22db27a532447b6745774 Mon Sep 17 00:00:00 2001 From: Yong-Siang Shih Date: Sat, 26 Oct 2024 05:39:02 -0400 Subject: [PATCH 3/4] Fix pip install static library not found errors for conda (#2683) Under certain circumstances Py_ENABLE_SHARED would be False but the static library cannot be found. For example, if you use conda environments or if you use specific versions of MacOS #2109, #2270. pip install would fail in such a situation. In this PR, we adjust the configurations so that the installation script would attempt to use shared library instead of the static one if no static library is found. This fixes pip install for Python 3.10.15 | packaged by conda-forge. It's not clear if this would fix the MacOS problems mentioned in the above issues though, as I am unable to verify those problems. --- plugins/python/uwsgiplugin.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/python/uwsgiplugin.py b/plugins/python/uwsgiplugin.py index b4e20c3e2..aa40e20e7 100644 --- a/plugins/python/uwsgiplugin.py +++ b/plugins/python/uwsgiplugin.py @@ -45,7 +45,8 @@ def get_python_version(): if not 'UWSGI_PYTHON_NOLIB' in os.environ: LIBS = sysconfig.get_config_var('LIBS').split() + sysconfig.get_config_var('SYSLIBS').split() # check if it is a non-shared build (but please, add --enable-shared to your python's ./configure script) - if not sysconfig.get_config_var('Py_ENABLE_SHARED'): + use_static_lib = not sysconfig.get_config_var('Py_ENABLE_SHARED') + if use_static_lib: libdir = sysconfig.get_config_var('LIBPL') # libdir does not exists, try to get it from the venv version = get_python_version() @@ -75,13 +76,17 @@ def get_python_version(): libpath = '%s/%s' % (libdir, sysconfig.get_config_var('LIBRARY')) if not os.path.exists(libpath): libpath = '%s/libpython%s.a' % (libdir, version) - LIBS.append(libpath) - # hack for messy linkers/compilers - if '-lutil' in LIBS: - LIBS.append('-lutil') - if '-lrt' in LIBS: - LIBS.append('-lrt') - else: + + if os.path.exists(libpath): + LIBS.append(libpath) + # hack for messy linkers/compilers + if '-lutil' in LIBS: + LIBS.append('-lutil') + if '-lrt' in LIBS: + LIBS.append('-lrt') + else: + use_static_lib = False + if not use_static_lib: try: libdir = sysconfig.get_config_var('LIBDIR') except: From b75b5d20342400a1e27d8d2dff5914c7ac8b9998 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 26 Oct 2024 11:52:03 +0200 Subject: [PATCH 4/4] Bump release to 2.0.28 --- PKG-INFO | 2 +- uwsgi.gemspec | 2 +- uwsgiconfig.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PKG-INFO b/PKG-INFO index 557dc6438..a0f7561f4 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: uWSGI -Version: 2.0.27 +Version: 2.0.28 Summary: The uWSGI server Home-page: https://uwsgi-docs.readthedocs.io/en/latest/ Author: Unbit diff --git a/uwsgi.gemspec b/uwsgi.gemspec index 1f7de23a0..3fc0230ea 100644 --- a/uwsgi.gemspec +++ b/uwsgi.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = 'uwsgi' s.license = 'GPL-2' s.version = `python -c "import uwsgiconfig as uc; print uc.uwsgi_version"`.sub(/-dev-.*/,'') - s.date = '2024-09-23' + s.date = '2024-10-26' s.summary = "uWSGI" s.description = "The uWSGI server for Ruby/Rack" s.authors = ["Unbit"] diff --git a/uwsgiconfig.py b/uwsgiconfig.py index 4642da0b2..6ba8fbc09 100644 --- a/uwsgiconfig.py +++ b/uwsgiconfig.py @@ -1,6 +1,6 @@ # uWSGI build system -uwsgi_version = '2.0.27' +uwsgi_version = '2.0.28' import os import re