refactor(epg): replace synchronous WebSocket send function with unified send_epg_update

Remove the _sd_send_ws_sync function and replace its usage with send_epg_update in the fetch_schedules_direct function. This change simplifies the code by ensuring all WebSocket updates are sent through a single function, improving maintainability and consistency in handling EPG updates.
This commit is contained in:
SergeantPanda 2026-06-05 10:42:39 -05:00
parent 7e906ad78e
commit 87b8035e06
3 changed files with 148 additions and 104 deletions

View file

@ -312,6 +312,34 @@ class TaskLockRenewer:
return False
def _is_gevent_monkey_patched():
try:
import gevent.monkey
return gevent.monkey.is_module_patched('threading')
except Exception:
return False
def _is_celery_worker_context():
"""True when executing inside an active Celery task (prefork worker)."""
try:
from celery import current_task
request = getattr(current_task, 'request', None)
return bool(request and getattr(request, 'id', None))
except Exception:
return False
def _should_use_sync_websocket_send():
"""
Use synchronous Redis delivery when gevent is monkey-patched but no gevent
hub is driving the process e.g. Celery prefork workers that inherit
gevent patching from uWSGI imports. gevent.spawn in that context schedules
coroutines that never run.
"""
return _is_gevent_monkey_patched() and _is_celery_worker_context()
def _gevent_ws_send(group_name, message):
"""
Publishes a WebSocket group message synchronously through Redis.
@ -363,6 +391,12 @@ def _gevent_ws_send(group_name, message):
logger.warning(f"Failed to send WebSocket update: {e}")
def send_websocket_update_sync(group_name, event_type, data):
"""Send a WebSocket group message synchronously via Redis (channels_redis wire format)."""
message = {'type': event_type, 'data': data}
_gevent_ws_send(group_name, message)
def send_websocket_update(group_name, event_type, data, collect_garbage=False):
"""
Sends a WebSocket group message.
@ -370,28 +404,25 @@ def send_websocket_update(group_name, event_type, data, collect_garbage=False):
In gevent-patched uWSGI workers, asyncio event loop creation fails because
monkey-patching removes select.epoll. For those contexts a synchronous Redis
path is used instead, matching the channels_redis 4.x wire format.
Celery prefork workers may inherit gevent monkey-patching without a running
gevent hub; in that case gevent.spawn would never execute, so delivery is
synchronous via Redis instead.
"""
channel_layer = get_channel_layer()
message = {'type': event_type, 'data': data}
def _do_send():
if _should_use_sync_websocket_send():
_gevent_ws_send(group_name, message)
elif _is_gevent_monkey_patched():
import gevent
gevent.spawn(_gevent_ws_send, group_name, message)
else:
# Not gevent-patched (plain Celery, tests) — use asyncio channel layer
try:
async_to_sync(channel_layer.group_send)(group_name, message)
async_to_sync(get_channel_layer().group_send)(group_name, message)
except Exception as e:
logger.warning(f"Failed to send WebSocket update: {e}")
try:
import gevent.monkey
if gevent.monkey.is_module_patched('threading'):
import gevent
gevent.spawn(_gevent_ws_send, group_name, message)
return
except Exception:
pass
# Not in a gevent-patched environment (Celery, tests) — use asyncio path
_do_send()
if collect_garbage:
gc.collect()