refactor(transcode): thread fs ops + atomic cache publish
- run blocking Path.exists/read_text off the event loop via anyio.to_thread - publish HLS renditions by building into a temp dir then atomic rename, so a reader never sees a playlist referencing half-written segments - per-writer temp name for opus so two concurrent jobs can't interleave - drop a track's cached renditions on delete so they don't dangle Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,13 @@ enqueues (e.g. several plays before the first finishes) are cheap. The DB sessio
|
||||
is released before ffmpeg runs; the track entity is a detached value object.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import anyio
|
||||
|
||||
from app.application.transcode_service import (
|
||||
HLS_BITRATE,
|
||||
bitrate_for_quality,
|
||||
@@ -27,6 +31,15 @@ from app.infrastructure.transcode.ffmpeg import FfmpegTranscoder
|
||||
log = get_logger("worker.transcode")
|
||||
|
||||
|
||||
def _swap_dir(tmp_dir: Path, final_dir: Path) -> None:
|
||||
"""Publish a freshly-built HLS rendition atomically: replace the final dir in
|
||||
one rename so a reader never sees a playlist referencing half-written
|
||||
segments. Any stale partial at the destination is cleared first."""
|
||||
if final_dir.exists():
|
||||
shutil.rmtree(final_dir)
|
||||
tmp_dir.replace(final_dir)
|
||||
|
||||
|
||||
async def transcode_track(
|
||||
_ctx: dict[str, Any],
|
||||
*,
|
||||
@@ -57,7 +70,12 @@ async def transcode_track(
|
||||
await transcoder.to_opus(src, dest, bitrate_kbps=bitrate)
|
||||
did_opus = True
|
||||
if hls and not hls_playlist_path(root, tid).exists():
|
||||
await transcoder.to_hls(src, hls_dir(root, tid), bitrate_kbps=HLS_BITRATE)
|
||||
# Build into a private temp dir, then swap it in atomically — the
|
||||
# cached playlist only becomes visible once every segment is written.
|
||||
final_dir = hls_dir(root, tid)
|
||||
tmp_dir = final_dir.with_name(f"hls.{uuid.uuid4().hex}.tmp")
|
||||
await transcoder.to_hls(src, tmp_dir, bitrate_kbps=HLS_BITRATE)
|
||||
await anyio.to_thread.run_sync(_swap_dir, tmp_dir, final_dir)
|
||||
did_hls = True
|
||||
|
||||
log.info("transcode_done", track_id=track_id, opus=did_opus, hls=did_hls)
|
||||
|
||||
Reference in New Issue
Block a user