fix: harden utility export recovery
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user