mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 00:55:50 +00:00
fix: Update plugin discovery comments and improve weakref handling in tests. Update changelog.
This commit is contained in:
parent
f38d3a4f17
commit
e61539fc80
3 changed files with 7 additions and 13 deletions
|
|
@ -91,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **Recurring rule edit modal showed a blank Channel field.** The `RecurringRuleModal` read channel data from a Zustand store slice (`channels`) that nothing populates - the channels page and all other consumers switched to on-demand summary fetches in a prior release. Opening the edit modal from the DVR page always produced an empty Channel select. The modal now fetches channels via `API.getChannelsSummary()` when it opens, matching the lightweight approach used by the one-time recording form and other modals.
|
||||
- **DVR section count badges appeared at the far right of the screen.** The "Currently Recording", "Upcoming Recordings", and "Previously Recorded" section headers wrapped the title and badge in a `Group justify="space-between"`, which pushed the badge to the opposite edge of the full-width container. Changed to `Group gap="xs" align="center"` so each badge sits inline with its heading.
|
||||
- **Plugin event dispatch aborted silently on first disabled plugin.** `trigger_event` in `apps/connect/utils.py` iterated `pm.list_plugins()` and, for disabled plugins, logged a debug message using `plugin.key` / `plugin.name` (attribute access). Because `list_plugins()` returns dicts, this raised `AttributeError` on the first disabled plugin encountered. Fixed by changing the two accesses to `plugin['key']` / `plugin['name']`. (Fixes #1231) - Thanks [@R3XCHRIS](https://github.com/R3XCHRIS)
|
||||
- **Plugin periodic tasks silently missed the first beat tick after every Celery worker restart.** Plugin modules live outside `INSTALLED_APPS` so `autodiscover_tasks()` never imports them, and worker startup skips plugin discovery via `should_skip_initialization()`. Any plugin using module-level `@shared_task` had its tasks unregistered with the worker until a lazy event import warmed the module; beat fired on schedule but the worker rejected with `Received unregistered task` and advanced `last_run_at` anyway, hiding the miss. A `worker_ready` hook in `dispatcharr/celery.py` now eagerly calls `PluginManager.discover_plugins(sync_db=False)` on every worker boot so plugin tasks are registered before beat starts firing. (Fixes #1244) - Thanks [@R3XCHRIS](https://github.com/R3XCHRIS)
|
||||
|
||||
### Performance
|
||||
|
||||
|
|
|
|||
|
|
@ -39,17 +39,9 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
|
|||
app.autodiscover_tasks()
|
||||
|
||||
|
||||
# Plugins live in /data/plugins/<slug>/plugin.py — outside INSTALLED_APPS,
|
||||
# so `app.autodiscover_tasks()` never imports them. Any plugin using
|
||||
# module-level `@shared_task` for cron work would therefore have its task
|
||||
# go unregistered on the worker side after every restart, until something
|
||||
# else (a `trigger_event` call, etc.) lazily imported the plugin module.
|
||||
# Beat would fire the periodic task on time and the worker would reject it
|
||||
# with `Received unregistered task`. (#1244)
|
||||
#
|
||||
# Eagerly call `PluginManager.discover_plugins` on worker startup so every
|
||||
# plugin's `@shared_task` is registered with this worker's Celery app
|
||||
# before beat starts firing.
|
||||
# Plugins live outside INSTALLED_APPS, so autodiscover_tasks() never imports
|
||||
# them. Without an eager import, workers reject plugin @shared_tasks with
|
||||
# "Received unregistered task" until a lazy event import warms the module.
|
||||
@worker_ready.connect(weak=False)
|
||||
def discover_plugins_on_worker_ready(**_kwargs):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ tick after every worker restart — the worker logs
|
|||
`Received unregistered task` and beat advances `last_run_at` anyway,
|
||||
hiding the failure. See #1244.
|
||||
"""
|
||||
import weakref
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
|
|
@ -67,8 +68,8 @@ class WorkerReadyPluginDiscoveryTests(SimpleTestCase):
|
|||
# The handler is registered with weak=False so the function appears
|
||||
# directly (not as a dead weakref).
|
||||
receivers = [r for _, r in worker_ready.receivers]
|
||||
# Handle both weakref-wrapped and direct receiver shapes.
|
||||
callables = [r() if hasattr(r, "__call__") and not callable(getattr(r, "__name__", None)) else r for r in receivers]
|
||||
# Dereference weakrefs; pass direct references through as-is.
|
||||
callables = [r() if isinstance(r, weakref.ref) else r for r in receivers]
|
||||
assert discover_plugins_on_worker_ready in receivers or \
|
||||
any(getattr(c, "__wrapped__", c) is discover_plugins_on_worker_ready for c in callables), (
|
||||
"discover_plugins_on_worker_ready was not connected to "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue