mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
Enhancement: Refactored app initialization to prevent redundant execution across multiple worker processes. Created dispatcharr.app_initialization utility module with should_skip_initialization() function that prevents custom initialization tasks (backup scheduler sync, developer notifications sync) from running during management commands, in worker processes, or in development servers. This significantly reduces startup overhead in multi-worker deployments (e.g., uWSGI with 10 workers now syncs the scheduler once instead of 10 times). Applied to both CoreConfig and BackupsConfig apps.
This commit is contained in:
parent
da5fbb1f5c
commit
de81dc6c80
4 changed files with 67 additions and 49 deletions
|
|
@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Changed
|
||||
|
||||
- XtreamCodes Authentication Optimization: Reduced API calls during XC refresh by 50% by eliminating redundant authentication step. This should help reduce rate-limiting errors.
|
||||
- App initialization efficiency: Refactored app initialization to prevent redundant execution across multiple worker processes. Created `dispatcharr.app_initialization` utility module with `should_skip_initialization()` function that prevents custom initialization tasks (backup scheduler sync, developer notifications sync) from running during management commands, in worker processes, or in development servers. This significantly reduces startup overhead in multi-worker deployments (e.g., uWSGI with 10 workers now syncs the scheduler once instead of 10 times). Applied to both `CoreConfig` and `BackupsConfig` apps.
|
||||
- M3U/EPG Network Access Defaults: Updated default network access settings for M3U and EPG endpoints to only allow local/private networks by default (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, ::1/128, fc00::/7, fe80::/10). This improves security by preventing public internet access to these endpoints unless explicitly configured. Other endpoints (Streams, XC API, UI) remain open by default.
|
||||
- Modular deployments: Bumped modular Postgres image to 17 and added compatibility checks (PostgreSQL version and UTF-8 database encoding) when using external databases to prevent migration/encoding issues.
|
||||
- Stream Identity Stability: Added `stream_id` (provider stream identifier) and `stream_chno` (provider channel number) fields to Stream model. For XC accounts, the stream hash now uses the stable `stream_id` instead of the URL when hashing, ensuring XC streams maintain their identity and channel associations even when account credentials or server URLs change. Supports both XC `num` and M3U `tvg-chno`/`channel-number` attributes.
|
||||
|
|
|
|||
|
|
@ -1,24 +1,10 @@
|
|||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
from django.apps import AppConfig
|
||||
import psutil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _is_worker_process():
|
||||
"""Check if this process is a worker spawned by uwsgi/gunicorn."""
|
||||
try:
|
||||
parent = psutil.Process(os.getppid())
|
||||
parent_name = parent.name()
|
||||
return parent_name in ['uwsgi', 'gunicorn']
|
||||
except Exception:
|
||||
# If we can't determine, assume it's not a worker (safe default)
|
||||
return False
|
||||
|
||||
|
||||
class BackupsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.backups"
|
||||
|
|
@ -26,25 +12,17 @@ class BackupsConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
"""Initialize backup scheduler on app startup."""
|
||||
# Skip management commands and celery
|
||||
skip_commands = ['celery', 'beat', 'migrate', 'makemigrations', 'shell', 'dbshell', 'collectstatic', 'loaddata']
|
||||
if any(cmd in sys.argv for cmd in skip_commands):
|
||||
from dispatcharr.app_initialization import should_skip_initialization
|
||||
|
||||
# Skip if this is a management command, worker process, or dev server
|
||||
if should_skip_initialization():
|
||||
return
|
||||
|
||||
# Skip daphne dev server
|
||||
if 'daphne' in sys.argv[0] if sys.argv else False:
|
||||
return
|
||||
|
||||
# Skip if this is a worker process spawned by uwsgi/gunicorn
|
||||
if _is_worker_process():
|
||||
return
|
||||
|
||||
# Proceed with syncing the backup scheduler
|
||||
logger.debug("Syncing backup scheduler on app startup")
|
||||
self._sync_backup_scheduler()
|
||||
|
||||
def _sync_backup_scheduler(self):
|
||||
"""Sync backup scheduler task to database."""
|
||||
# Import here to avoid circular imports
|
||||
from core.models import CoreSettings
|
||||
from .scheduler import _sync_periodic_task, DEFAULTS
|
||||
try:
|
||||
|
|
|
|||
31
core/apps.py
31
core/apps.py
|
|
@ -1,6 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
import os, logging
|
||||
import logging
|
||||
|
||||
# Define TRACE level (5 is below DEBUG which is 10)
|
||||
TRACE = 5
|
||||
|
|
@ -15,6 +15,7 @@ def trace(self, message, *args, **kwargs):
|
|||
# Add the trace method to the Logger class
|
||||
logging.Logger.trace = trace
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core'
|
||||
|
|
@ -22,13 +23,15 @@ class CoreConfig(AppConfig):
|
|||
def ready(self):
|
||||
# Import signals to ensure they get registered
|
||||
import core.signals
|
||||
from dispatcharr.app_initialization import should_skip_initialization
|
||||
|
||||
# Sync developer notifications and check for version updates on startup
|
||||
# Only run in the main process (not in management commands or migrations)
|
||||
import sys
|
||||
if 'runserver' in sys.argv or 'uwsgi' in sys.argv[0] if sys.argv else False:
|
||||
self._sync_developer_notifications()
|
||||
self._check_version_update()
|
||||
# Only run in the main process (not in management commands, migrations, or workers)
|
||||
if should_skip_initialization():
|
||||
return
|
||||
|
||||
self._sync_developer_notifications()
|
||||
self._check_version_update()
|
||||
|
||||
def _sync_developer_notifications(self):
|
||||
"""Sync developer notifications from JSON file to database."""
|
||||
|
|
@ -37,22 +40,6 @@ class CoreConfig(AppConfig):
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Check if tables exist (avoid running during migrations)
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT 1 FROM information_schema.tables WHERE table_name = 'core_systemnotification'"
|
||||
)
|
||||
if not cursor.fetchone():
|
||||
# For SQLite
|
||||
cursor.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='core_systemnotification'"
|
||||
)
|
||||
if not cursor.fetchone():
|
||||
return
|
||||
except Exception:
|
||||
# If we can't check, the table might not exist yet
|
||||
pass
|
||||
|
||||
try:
|
||||
from core.developer_notifications import sync_developer_notifications
|
||||
|
|
|
|||
52
dispatcharr/app_initialization.py
Normal file
52
dispatcharr/app_initialization.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Utilities for managing app initialization across multiple processes."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import psutil
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _is_worker_process():
|
||||
"""Check if this process is a worker spawned by uwsgi/gunicorn."""
|
||||
try:
|
||||
parent = psutil.Process(os.getppid())
|
||||
parent_name = parent.name()
|
||||
return parent_name in ['uwsgi', 'gunicorn']
|
||||
except Exception:
|
||||
# If we can't determine, assume it's not a worker (safe default)
|
||||
return False
|
||||
|
||||
|
||||
def should_skip_initialization():
|
||||
"""
|
||||
Determine if app initialization should be skipped in this process.
|
||||
|
||||
Returns True if:
|
||||
- A management command is being run (migrate, celery, shell, etc.)
|
||||
- The development server (daphne) is running
|
||||
- This is a worker process (not the master)
|
||||
|
||||
This prevents redundant initialization across multiple worker processes.
|
||||
"""
|
||||
# Skip management commands and background services
|
||||
skip_commands = [
|
||||
'celery', 'beat', 'migrate', 'makemigrations', 'shell', 'dbshell',
|
||||
'collectstatic', 'loaddata'
|
||||
]
|
||||
if any(cmd in sys.argv for cmd in skip_commands):
|
||||
logger.debug(f"Skipping initialization due to command: {sys.argv}")
|
||||
return True
|
||||
|
||||
# Skip daphne development server (single process, no need to guard)
|
||||
if 'daphne' in sys.argv[0] if sys.argv else False:
|
||||
logger.debug(f"Skipping initialization in daphne development server. Command: {sys.argv}")
|
||||
return True
|
||||
|
||||
# Skip if this is a worker process spawned by uwsgi/gunicorn
|
||||
if _is_worker_process():
|
||||
logger.debug(f"Skipping initialization in worker process. Command: {sys.argv}")
|
||||
return True
|
||||
|
||||
return False
|
||||
Loading…
Add table
Add a link
Reference in a new issue