mirror of
https://github.com/alexta69/metube.git
synced 2026-07-17 16:47:30 +00:00
Addresses a full-project review. Backend correctness and availability: - ytdl: cancel() only SIGKILLs the child's process group when the child actually became its own group leader, so a race (or failed setpgrp) can no longer kill the whole server; kill the group on cancel and on shutdown to avoid orphaned ffmpeg children - ytdl: dedicated ThreadPoolExecutor for download supervision so active downloads can't starve extract_info / live probes on the default pool - ytdl/main/subscriptions: route fire-and-forget tasks through a bg_tasks helper that keeps a strong ref and logs failures - subscriptions: run flat-playlist extraction in an executor and check feeds with bounded concurrency so one slow feed can't block the loop; set last_checked on failure so broken feeds aren't retried every 60s - main: validate ids on /start & /delete and numeric env vars at startup; return 400 (not 500) on bad subscriptions/update input; serve /history from memory; move get_custom_dirs off the event loop; restrict t= stripping to YouTube hosts; drop double percent-decode in state guard - dl_formats/ytdl: enforce requested caption format via FFmpegSubtitlesConvertor and strip VTT header metadata only in the pre-cue region so real dialogue is preserved - ytdl: throttle progress events, dedup adds against pending, clear filename/size on error and reject out-of-dir trashcan deletes, pin fork start-method on Linux only Frontend: - retry deletes the done record only after a successful re-add - surface HTTP errors for delete/start and reset the deleting flag - ignore late 'updated' events for rows no longer in the queue - track table rows by map key; FileSizePipe uses base-1024 Also: HTTPS-aware Docker healthcheck, dead-code removal, and shared helpers for path-containment and yt-dlp option merging. Adds/updates unit tests throughout (250 backend tests passing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
26 lines
939 B
Python
26 lines
939 B
Python
import asyncio
|
|
import logging
|
|
|
|
log = logging.getLogger("bg_tasks")
|
|
_TASKS: set[asyncio.Task] = set()
|
|
|
|
|
|
def create_task(coro, *, name: str | None = None) -> asyncio.Task:
|
|
"""create_task that keeps a strong reference and logs unexpected failures.
|
|
|
|
A bare ``asyncio.create_task(...)`` is only weakly referenced by the event
|
|
loop; if nothing else holds the returned Task, it can be garbage collected
|
|
mid-flight. Keeping a module-level strong reference (removed once the task
|
|
finishes) avoids that, and the done-callback surfaces otherwise-silent
|
|
failures.
|
|
"""
|
|
task = asyncio.get_running_loop().create_task(coro, name=name)
|
|
_TASKS.add(task)
|
|
|
|
def _done(t: asyncio.Task) -> None:
|
|
_TASKS.discard(t)
|
|
if not t.cancelled() and t.exception() is not None:
|
|
log.error("Background task %s failed", t.get_name(), exc_info=t.exception())
|
|
|
|
task.add_done_callback(_done)
|
|
return task
|