Merge pull request #3602 from rommapp/fix/webp-convert-on-startup

fix(webp): backfill cover conversion on startup when enabled
This commit is contained in:
Georges-Antoine Assi
2026-06-25 16:09:47 -04:00
committed by GitHub
2 changed files with 78 additions and 0 deletions

View File

@@ -49,6 +49,7 @@ from utils.context import initialize_context
tracer = trace.get_tracer(__name__)
RECOMPUTE_SAVE_HASHES_JOB_ID = "recompute_save_content_hashes_bootstrap"
CONVERT_IMAGES_TO_WEBP_JOB_ID = "convert_images_to_webp_bootstrap"
def _enqueue_recompute_save_hashes_if_needed() -> None:
@@ -98,6 +99,38 @@ def _enqueue_recompute_save_hashes_if_needed() -> None:
)
def _enqueue_convert_images_to_webp() -> None:
"""Backfill .webp covers when WebP conversion is enabled.
The frontend rewrites cover URLs to .webp as soon as the feature flag is
on, but the scheduled task only runs at its next cron time and the inline
conversion in the resources handler only fires for covers fetched after
enabling. Without a backfill, existing covers have no .webp sibling and
every request 404s until the cron eventually runs."""
try:
if Job.exists(CONVERT_IMAGES_TO_WEBP_JOB_ID, low_prio_queue.connection):
log.info(
"convert_images_to_webp already queued or running from a "
"previous restart; skipping enqueue"
)
return
low_prio_queue.enqueue(
convert_images_to_webp_task.run,
job_id=CONVERT_IMAGES_TO_WEBP_JOB_ID,
job_timeout=TASK_TIMEOUT,
meta={
"task_name": convert_images_to_webp_task.title,
"task_type": convert_images_to_webp_task.task_type.value,
},
)
log.info("Enqueued convert_images_to_webp backfill on low-priority worker")
except Exception:
log.exception(
"Failed to enqueue convert_images_to_webp; admins can run it manually"
)
@tracer.start_as_current_span("main")
async def main() -> None:
"""Run startup tasks."""
@@ -121,6 +154,7 @@ async def main() -> None:
if ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP:
log.info("Starting scheduled convert images to webp")
convert_images_to_webp_task.init()
_enqueue_convert_images_to_webp()
if ENABLE_SCHEDULED_RETROACHIEVEMENTS_PROGRESS_SYNC:
log.info("Starting scheduled RetroAchievements progress sync")
sync_retroachievements_progress_task.init()

View File

@@ -88,3 +88,47 @@ def test_enqueue_recompute_swallows_enqueue_error(mocker):
)
startup._enqueue_recompute_save_hashes_if_needed()
def test_enqueue_convert_webp_fires_when_not_queued(mocker):
"""No in-flight bootstrap job -> enqueue the backfill exactly once."""
mocker.patch.object(startup.Job, "exists", return_value=False)
enqueue = mocker.patch.object(startup.low_prio_queue, "enqueue")
startup._enqueue_convert_images_to_webp()
enqueue.assert_called_once()
args, kwargs = enqueue.call_args
assert args[0].__self__ is startup.convert_images_to_webp_task
assert kwargs["meta"]["task_name"] == startup.convert_images_to_webp_task.title
assert kwargs["meta"]["task_type"] == (
startup.convert_images_to_webp_task.task_type.value
)
assert kwargs["job_timeout"] == startup.TASK_TIMEOUT
assert kwargs["job_id"] == startup.CONVERT_IMAGES_TO_WEBP_JOB_ID
def test_convert_webp_job_id_is_valid_rq_id():
"""An invalid job_id raises in set_id, which the broad except swallows ->
backfill silently never enqueues. Assert the id matches RQ's contract."""
assert JOB_ID_PATTERN.fullmatch(startup.CONVERT_IMAGES_TO_WEBP_JOB_ID)
def test_enqueue_convert_webp_skips_when_already_queued(mocker):
"""An in-flight job from a previous restart -> skip enqueue, don't double up."""
mocker.patch.object(startup.Job, "exists", return_value=True)
enqueue = mocker.patch.object(startup.low_prio_queue, "enqueue")
startup._enqueue_convert_images_to_webp()
enqueue.assert_not_called()
def test_enqueue_convert_webp_swallows_enqueue_error(mocker):
"""A failed enqueue must not crash startup."""
mocker.patch.object(startup.Job, "exists", return_value=False)
mocker.patch.object(
startup.low_prio_queue, "enqueue", side_effect=RuntimeError("redis gone")
)
startup._enqueue_convert_images_to_webp()