mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 00:55:50 +00:00
Merge pull request #1026 from endoze/fix-modular-worker-trigger-issue
fix(wsgi): ensure gevent monkey-patching before Django import chain
This commit is contained in:
commit
7da7ba97a9
4 changed files with 55 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ 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.
|
||||
|
|
|
|||
0
dispatcharr/tests/__init__.py
Normal file
0
dispatcharr/tests/__init__.py
Normal file
39
dispatcharr/tests/test_wsgi.py
Normal file
39
dispatcharr/tests/test_wsgi.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""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}",
|
||||
)
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue