diff -uNr a/core/init.c b/core/init.c --- a/core/init.c 2024-06-01 20:40:38.000000000 +0200 +++ b/core/init.c 2024-08-13 09:56:13.152870522 +0200 @@ -432,8 +432,8 @@ uwsgi.cores = uwsgi.async; } + uwsgi.has_threads = 1; if (uwsgi.threads > 1) { - uwsgi.has_threads = 1; uwsgi.cores = uwsgi.threads; } diff -uNr a/core/uwsgi.c b/core/uwsgi.c --- a/core/uwsgi.c 2024-06-01 20:40:38.000000000 +0200 +++ b/core/uwsgi.c 2024-08-13 09:56:13.156870522 +0200 @@ -197,7 +197,7 @@ {"freebind", no_argument, 0, "put socket in freebind mode", uwsgi_opt_true, &uwsgi.freebind, 0}, #endif {"map-socket", required_argument, 0, "map sockets to specific workers", uwsgi_opt_add_string_list, &uwsgi.map_socket, 0}, - {"enable-threads", no_argument, 'T', "enable threads", uwsgi_opt_true, &uwsgi.has_threads, 0}, + {"enable-threads", no_argument, 'T', "enable threads (stub option this is true by default)", uwsgi_opt_true, &uwsgi.has_threads, 0}, {"no-threads-wait", no_argument, 0, "do not wait for threads cancellation on quit/reload", uwsgi_opt_true, &uwsgi.no_threads_wait, 0}, {"auto-procname", no_argument, 0, "automatically set processes name to something meaningful", uwsgi_opt_true, &uwsgi.auto_procname, 0}, diff -uNr a/.github/workflows/test.yml b/.github/workflows/test.yml --- a/.github/workflows/test.yml 2024-06-01 20:40:38.000000000 +0200 +++ b/.github/workflows/test.yml 2024-08-13 09:56:13.152870522 +0200 @@ -37,7 +37,7 @@ runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] test-suite: [python, deadlocks] steps: - name: Add deadnakes ppa diff -uNr a/plugins/python/python_plugin.c b/plugins/python/python_plugin.c --- a/plugins/python/python_plugin.c 2024-08-13 09:55:30.374870522 +0200 +++ b/plugins/python/python_plugin.c 2024-08-13 10:00:36.315870522 +0200 @@ -406,7 +406,7 @@ // reset python signal flags so child processes can trap signals // Necessary if uwsgi fork hooks not called to update interpreter state if (!up.call_uwsgi_fork_hooks && up.call_osafterfork) { -#ifdef HAS_NOT_PyOS_AfterFork_Child +#ifdef HAS_NOT_PYOS_FORK_STABLE_API PyOS_AfterFork(); #else PyOS_AfterFork_Child(); @@ -1340,11 +1340,10 @@ // Acquire the gil and import lock before forking in order to avoid // deadlocks in workers UWSGI_GET_GIL -#if defined UWSGI_PY312 - PyInterpreterState *interp = PyInterpreterState_Get(); - _PyImport_AcquireLock(interp); -#else +#ifdef HAS_NOT_PYOS_FORK_STABLE_API _PyImport_AcquireLock(); +#else + PyOS_BeforeFork(); #endif } } @@ -1357,17 +1356,16 @@ if (uwsgi.has_threads) { if (step == 0) { // Release locks within master process -#if defined UWSGI_PY312 - PyInterpreterState *interp = PyInterpreterState_Get(); - _PyImport_ReleaseLock(interp); -#else +#ifdef HAS_NOT_PYOS_FORK_STABLE_API _PyImport_ReleaseLock(); +#else + PyOS_AfterFork_Parent(); #endif UWSGI_RELEASE_GIL } else { // Ensure thread state and locks are cleaned up in child process -#ifdef HAS_NOT_PyOS_AfterFork_Child +#ifdef HAS_NOT_PYOS_FORK_STABLE_API PyOS_AfterFork(); #else PyOS_AfterFork_Child(); @@ -1618,7 +1616,11 @@ PyGILState_Release(pgst); if (wsgi_req) { -#ifdef UWSGI_PY312 +#ifdef UWSGI_PY313 + up.current_c_recursion_remaining[wsgi_req->async_id] = tstate->c_recursion_remaining; + up.current_py_recursion_remaining[wsgi_req->async_id] = tstate->py_recursion_remaining; + up.current_frame[wsgi_req->async_id] = tstate->current_frame; +#elif defined UWSGI_PY312 up.current_c_recursion_remaining[wsgi_req->async_id] = tstate->c_recursion_remaining; up.current_py_recursion_remaining[wsgi_req->async_id] = tstate->py_recursion_remaining; up.current_frame[wsgi_req->async_id] = tstate->cframe; @@ -1631,7 +1633,11 @@ #endif } else { -#ifdef UWSGI_PY312 +#ifdef UWSGI_PY313 + up.current_main_c_recursion_remaining = tstate->c_recursion_remaining; + up.current_main_py_recursion_remaining = tstate->py_recursion_remaining; + up.current_main_frame = tstate->current_frame; +#elif defined UWSGI_PY312 up.current_main_c_recursion_remaining = tstate->c_recursion_remaining; up.current_main_py_recursion_remaining = tstate->py_recursion_remaining; up.current_main_frame = tstate->cframe; @@ -1871,7 +1877,11 @@ PyGILState_Release(pgst); if (wsgi_req) { -#ifdef UWSGI_PY312 +#ifdef UWSGI_PY313 + tstate->c_recursion_remaining = up.current_c_recursion_remaining[wsgi_req->async_id]; + tstate->py_recursion_remaining = up.current_py_recursion_remaining[wsgi_req->async_id]; + tstate->current_frame = up.current_frame[wsgi_req->async_id]; +#elif defined UWSGI_PY312 tstate->c_recursion_remaining = up.current_c_recursion_remaining[wsgi_req->async_id]; tstate->py_recursion_remaining = up.current_py_recursion_remaining[wsgi_req->async_id]; tstate->cframe = up.current_frame[wsgi_req->async_id]; @@ -1884,7 +1894,11 @@ #endif } else { -#ifdef UWSGI_PY312 +#ifdef UWSGI_PY313 + tstate->c_recursion_remaining = up.current_main_c_recursion_remaining; + tstate->py_recursion_remaining = up.current_main_py_recursion_remaining; + tstate->current_frame = up.current_main_frame; +#elif defined UWSGI_PY312 tstate->c_recursion_remaining = up.current_main_c_recursion_remaining; tstate->py_recursion_remaining = up.current_main_py_recursion_remaining; tstate->cframe = up.current_main_frame; @@ -2098,7 +2112,7 @@ // ensure signals can be used again from python // Necessary if fork hooks have been not used to update interpreter state if (!up.call_osafterfork && !up.call_uwsgi_fork_hooks) -#ifdef HAS_NOT_PyOS_AfterFork_Child +#ifdef HAS_NOT_PYOS_FORK_STABLE_API PyOS_AfterFork(); #else PyOS_AfterFork_Child(); diff -uNr a/plugins/python/uwsgi_python.h b/plugins/python/uwsgi_python.h --- a/plugins/python/uwsgi_python.h 2024-06-01 20:40:38.000000000 +0200 +++ b/plugins/python/uwsgi_python.h 2024-08-13 10:04:58.492870522 +0200 @@ -25,6 +25,10 @@ # define UWSGI_PY312 #endif +#if (PY_VERSION_HEX >= 0x030d0000) +# define UWSGI_PY313 +#endif + #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 7 #define HAS_NOT_PyMemoryView_FromBuffer #endif @@ -41,12 +45,8 @@ #define HAS_NO_ERRORS_IN_PyFile_FromFd #endif -#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 7 -#define HAS_NOT_PyOS_AfterFork_Child -#endif - -#if PY_MAJOR_VERSION < 3 -#define HAS_NOT_PyOS_AfterFork_Child +#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 7) || PY_MAJOR_VERSION < 3 +#define HAS_NOT_PYOS_FORK_STABLE_API #endif #if PY_MAJOR_VERSION > 2 @@ -172,7 +172,15 @@ char *callable; -#ifdef UWSGI_PY312 +#ifdef UWSGI_PY313 + int *current_c_recursion_remaining; + int *current_py_recursion_remaining; + struct _PyInterpreterFrame **current_frame; + + int current_main_c_recursion_remaining; + int current_main_py_recursion_remaining; + struct _PyInterpreterFrame *current_main_frame; +#elif defined UWSGI_PY312 int *current_c_recursion_remaining; int *current_py_recursion_remaining; _PyCFrame **current_frame;