mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-23 01:58:00 +00:00
This update addresses issues related to geventpool database connection leaks by consistently invoking `close_old_connections()` in `finally` blocks within the backup scheduler, plugin repository refresh, and core notification synchronization processes. These changes ensure that idle connections are properly released, preventing resource exhaustion. HLS proxy server has been disabled (hasn't been used). Additionally, the proxy server cleanup logic has been enhanced to safely handle the absence of proxy instances. Tests have been updated to validate these improvements and ensure stability in high-load scenarios.
123 lines
5.3 KiB
Python
123 lines
5.3 KiB
Python
from django.apps import AppConfig
|
|
import os
|
|
import sys
|
|
from django.db.models.signals import post_migrate
|
|
|
|
|
|
class PluginsConfig(AppConfig):
|
|
name = "apps.plugins"
|
|
verbose_name = "Plugins"
|
|
|
|
def ready(self):
|
|
"""Wire up plugin discovery without hitting the DB during app init.
|
|
|
|
- Skip during common management commands that don't need discovery.
|
|
- Register post_migrate handler to sync plugin registry to DB after migrations.
|
|
- Run in-memory discovery (no DB) in every non-Celery process so plugin
|
|
modules are imported and monkey-patches apply. This includes uWSGI workers
|
|
under lazy-apps=true, which each start cold and never inherit a warmed fork.
|
|
Celery workers skip here and discover via the worker_ready signal instead.
|
|
- One-shot startup tasks (schedule setup, repo refresh) are gated by
|
|
should_skip_initialization() so they only fire in the master/main process.
|
|
"""
|
|
try:
|
|
# Allow explicit opt-out via env var
|
|
if os.environ.get("DISPATCHARR_SKIP_PLUGIN_AUTODISCOVERY", "").lower() in ("1", "true", "yes"):
|
|
return
|
|
|
|
argv = sys.argv[1:] if len(sys.argv) > 1 else []
|
|
mgmt_cmds_to_skip = {
|
|
# Skip immediate discovery for these commands
|
|
"makemigrations", "collectstatic", "check", "test", "shell", "showmigrations",
|
|
}
|
|
if argv and argv[0] in mgmt_cmds_to_skip:
|
|
return
|
|
|
|
# Run discovery with DB sync after the plugins app has been migrated
|
|
def _post_migrate_discover(sender=None, app_config=None, **kwargs):
|
|
try:
|
|
if app_config and getattr(app_config, 'label', None) != 'plugins':
|
|
return
|
|
from .loader import PluginManager
|
|
PluginManager.get().discover_plugins(sync_db=True)
|
|
except Exception:
|
|
import logging
|
|
logging.getLogger(__name__).exception("Plugin discovery failed in post_migrate")
|
|
|
|
post_migrate.connect(
|
|
_post_migrate_discover,
|
|
dispatch_uid="apps.plugins.post_migrate_discover",
|
|
)
|
|
|
|
from dispatcharr.app_initialization import should_skip_initialization
|
|
# Skip discovery for Celery (worker_ready signal handles it) and
|
|
# pure management commands that don't serve requests. Every other
|
|
# process - including each uWSGI worker under lazy-apps=true - runs
|
|
# discovery so plugin modules are imported and monkey-patches apply.
|
|
_no_discovery_cmds = {'celery', 'beat', 'migrate', 'dbshell', 'loaddata'}
|
|
if not any(cmd in sys.argv for cmd in _no_discovery_cmds):
|
|
from .loader import PluginManager
|
|
PluginManager.get().discover_plugins(sync_db=False)
|
|
|
|
if should_skip_initialization():
|
|
return
|
|
except Exception:
|
|
# Avoid breaking startup due to plugin errors
|
|
import logging
|
|
|
|
logging.getLogger(__name__).exception("Plugin discovery wiring failed during app ready")
|
|
|
|
# Register periodic task for refreshing plugin repo manifests
|
|
self._setup_repo_refresh_schedule()
|
|
|
|
# Refresh repo manifests once at startup so the UI always has current data
|
|
self._enqueue_startup_refresh()
|
|
|
|
def _enqueue_startup_refresh(self):
|
|
from dispatcharr.app_initialization import should_skip_initialization
|
|
if should_skip_initialization():
|
|
return
|
|
try:
|
|
from .tasks import refresh_plugin_repos
|
|
refresh_plugin_repos.apply_async(countdown=10)
|
|
except Exception:
|
|
import logging
|
|
logging.getLogger(__name__).debug(
|
|
"Could not enqueue startup plugin repo refresh (Celery may not be ready yet)"
|
|
)
|
|
|
|
def _setup_repo_refresh_schedule(self):
|
|
from django.db import close_old_connections
|
|
|
|
from dispatcharr.app_initialization import should_skip_initialization
|
|
if should_skip_initialization():
|
|
return
|
|
try:
|
|
from core.scheduling import create_or_update_periodic_task, delete_periodic_task
|
|
from core.models import CoreSettings
|
|
from .tasks import PLUGIN_REPO_REFRESH_TASK_NAME
|
|
|
|
interval = 6
|
|
try:
|
|
obj = CoreSettings.objects.get(key="plugin_repo_settings")
|
|
interval = obj.value.get("refresh_interval_hours", 6)
|
|
except CoreSettings.DoesNotExist:
|
|
pass
|
|
|
|
if interval == 0:
|
|
delete_periodic_task(PLUGIN_REPO_REFRESH_TASK_NAME)
|
|
else:
|
|
create_or_update_periodic_task(
|
|
task_name=PLUGIN_REPO_REFRESH_TASK_NAME,
|
|
celery_task_path="apps.plugins.tasks.refresh_plugin_repos",
|
|
interval_hours=interval,
|
|
enabled=True,
|
|
)
|
|
except Exception:
|
|
import logging
|
|
logging.getLogger(__name__).debug(
|
|
"Could not set up plugin repo refresh schedule (migrations may not have run yet)"
|
|
)
|
|
finally:
|
|
# Boot ORM runs outside a request cycle; return geventpool checkouts.
|
|
close_old_connections()
|