fix: harden utility export recovery

This commit is contained in:
Media
2026-08-12 18:59:48 -05:00
parent d339e10c47
commit e924b0d099
7 changed files with 71 additions and 14 deletions
+4 -1
View File
@@ -314,7 +314,10 @@ def load_config(
max_queued_exports=_integer(
downloads_raw.get("max_queued_exports", 20),
"downloads.max_queued_exports",
maximum=1000,
# The web status endpoint deliberately returns at most 100 rows.
# Keep the queue bound within that view so every active job remains
# visible even when the configured queue is full.
maximum=100,
),
)
if downloads.cookie_required and downloads.cookie_path is None:
+25 -2
View File
@@ -305,13 +305,29 @@ class DownloadManager:
future.add_done_callback(self._forget_future)
return reservation
def schedule_export_reconciliation(self) -> bool:
"""Queue crash-artifact recovery without delaying core service startup."""
with self._lock:
if self._stopping:
return False
try:
future = self._export_executor.submit(self.reconcile_export_artifacts)
except BaseException:
LOG.exception("could not schedule utility export reconciliation")
return False
with self._lock:
self._futures.add(future)
future.add_done_callback(self._forget_future)
return True
def _forget_future(self, future: Future[Any]) -> None:
with self._lock:
self._futures.discard(future)
try:
future.result()
except BaseException:
LOG.exception("download worker escaped its failure boundary")
LOG.exception("media worker escaped its failure boundary")
def _emit(self, event: DownloadEvent) -> None:
try:
@@ -839,7 +855,14 @@ class DownloadManager:
and source_info.pixel_format in {"yuv420p", "yuvj420p"}
)
audio_compatible = source_info.audio_codec == "aac"
container_compatible = "mp4" in source_info.format_names
# ffprobe reports the same comma-separated demuxer aliases for MOV and
# MP4. A .mov source therefore needs at least a stream-copy remux; it
# must never be renamed to .mp4 solely because the alias list includes
# "mp4".
container_compatible = (
source.suffix.lower() == ".mp4"
and "mp4" in source_info.format_names
)
if video_compatible and audio_compatible and container_compatible:
os.replace(source, output)
else:
+5 -6
View File
@@ -148,12 +148,6 @@ class ControllerService:
self.store,
on_event=self._queue_download_event,
)
try:
self.downloads.reconcile_export_artifacts()
except BaseException:
LOG.exception(
"utility export reconciliation failed; Controller startup continues"
)
# Cleanup is deliberately deferred until fresh Player evidence
# (or an explicit administrative reconciliation) makes it safe.
self._reset_cleanup_evidence()
@@ -171,6 +165,11 @@ class ControllerService:
self._coordinator.start()
self.mqtt.start()
self._started = True
# A crash-published playlist may contain many files to probe.
# Queue that bounded work only after core readiness and keep it
# on the dedicated export executor, so MQTT reconnect and
# Player coordination never wait for recovery to finish.
self.downloads.schedule_export_reconciliation()
except BaseException:
self._stop_event.set()
try: