From d487bb5671236877c28dfe1e148e7072c9a15fe2 Mon Sep 17 00:00:00 2001 From: SergeantPanda <61642231+SergeantPanda@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:33:55 -0600 Subject: [PATCH] Revert "fix(wsgi): ensure gevent monkey-patching before Django import chain" --- CHANGELOG.md | 1 - dispatcharr/tests/__init__.py | 0 dispatcharr/tests/test_wsgi.py | 39 ---------------------------------- dispatcharr/wsgi.py | 15 ------------- 4 files changed, 55 deletions(-) delete mode 100644 dispatcharr/tests/__init__.py delete mode 100644 dispatcharr/tests/test_wsgi.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fac12c..fadc44e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Celery Redis connections using unpatched sockets under gevent: `dispatcharr/__init__.py` imports `celery.py` as part of `django.setup()`, initialising the Redis connection pool before uWSGI's gevent plugin patches stdlib sockets. Added `gevent.monkey.patch_all()` at the top of `wsgi.py` to guarantee all broker connections are gevent-aware. - Thanks [@endoze](https://github.com/endoze) - TS proxy streams dying after 30–200 seconds in multi-worker uWSGI/Celery deployments, caused by three interrelated bugs. (Fixes #992, #980) - Thanks [@PFalko](https://github.com/PFalko) - **Double ProxyServer instantiation**: `ProxyConfig.ready()` called `TSProxyServer()` directly while `TSProxyConfig.ready()` also called `TSProxyServer.get_instance()`, creating two instances per worker — each with its own cleanup thread. The orphaned thread could not extend ownership because it had no entries in `stream_managers`. Fixed by using `TSProxyServer.get_instance()` in `ProxyConfig.ready()`. - **`flushdb()` on every Redis client init**: `RedisClient.get_client()` called `client.flushdb()` whenever `_client` was `None`. Celery autoscale (`--autoscale=6,1`) spawning new workers mid-stream triggered this path, nuking all Redis keys including active ownership keys, client records, and channel metadata. Removed the `flushdb()` call entirely. diff --git a/dispatcharr/tests/__init__.py b/dispatcharr/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/dispatcharr/tests/test_wsgi.py b/dispatcharr/tests/test_wsgi.py deleted file mode 100644 index cdf7a2e9..00000000 --- a/dispatcharr/tests/test_wsgi.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Tests for dispatcharr/wsgi.py gevent monkey-patching.""" - -import os -import subprocess -import sys -import unittest - - -class TestWSGIGeventPatching(unittest.TestCase): - """Verify that wsgi.py applies gevent monkey-patching before Django imports. - - These tests run in subprocesses because monkey.patch_all() must execute - before any other imports. By the time Django's test runner loads this - file, ssl/socket are already imported, so calling patch_all() in-process - would fail. A subprocess mirrors how uWSGI actually loads wsgi.py. - """ - - def test_socket_is_patched_after_wsgi_import(self): - """Loading wsgi.py first (as uWSGI does) should patch sockets.""" - result = subprocess.run( - [ - sys.executable, "-c", - "import dispatcharr.wsgi; " - "from gevent import monkey; " - "assert monkey.is_module_patched('socket'), " - "'socket module was not patched by wsgi.py'; " - "print('PASS')", - ], - capture_output=True, - text=True, - env={**os.environ, "DJANGO_SETTINGS_MODULE": "dispatcharr.settings"}, - timeout=60, - cwd="/app", - ) - self.assertEqual( - result.returncode, - 0, - f"monkey-patching check failed:\n{result.stderr}", - ) diff --git a/dispatcharr/wsgi.py b/dispatcharr/wsgi.py index 6e8860af..29c2ba01 100644 --- a/dispatcharr/wsgi.py +++ b/dispatcharr/wsgi.py @@ -1,21 +1,6 @@ """ WSGI config for dispatcharr project. """ -# When running under uWSGI with gevent, ensure monkey-patching is fully -# applied before any other imports. Django's setup triggers the import of -# dispatcharr/__init__.py → celery.py, which initialises the Celery broker -# transport. If those imports happen before sockets are patched, Celery's -# Redis connection pool holds unpatched sockets that silently fail under -# gevent's cooperative scheduling. Calling patch_all() here—before the -# Django import chain—guarantees every socket (including pooled broker -# connections) is gevent-aware. The call is idempotent, so it's harmless -# if uWSGI's gevent plugin has already patched. -try: - from gevent import monkey - monkey.patch_all() -except ImportError: - pass - import os from django.core.wsgi import get_wsgi_application