from datetime import timedelta from uuid import uuid4 from django.conf import settings from django.core.cache import cache from django.tasks import task from django.utils import timezone from .services.steam_store import refresh_steam_games # Set while a periodic refresh is queued, so there's only ever one loop running, # whatever the number of workers/containers trying to start it. It expires on # its own if the loop dies (worker killed mid-job, Redis queue flushed, ...), # letting the next schedule_steam_refresh() start a new one. The guard holds the # loop id, so if it expires while the old loop is just late, the old one stops # at its next run (instead of 2 loops running side by side). STEAM_REFRESH_GUARD_KEY = "games:steam_refresh:scheduled" def steam_refresh_interval() -> timedelta: return timedelta(minutes=settings.STEAM_STORE_REFRESH_MINUTES) def _guard_timeout() -> int: return int(steam_refresh_interval().total_seconds() * 2) def schedule_steam_refresh() -> bool: """Start the periodic refresh loop right away, unless one is already queued. Safe to call from anywhere, any number of times.""" loop_id = uuid4().hex if not cache.add(STEAM_REFRESH_GUARD_KEY, loop_id, timeout=_guard_timeout()): return False refresh_steam_games_task.enqueue(loop_id=loop_id) return True @task def refresh_steam_games_task( game_ids: list[int] | None = None, loop_id: str | None = None ): """Refresh Steam store info, then re-enqueue itself every STEAM_STORE_REFRESH_MINUTES (same self-rescheduling idea as sync_event_game_scopes_task, no external scheduler needed). Without loop_id it's a one-off run (e.g. the admin action) that leaves the periodic loop alone. With loop_id, it only continues while the guard still holds this loop_id.""" if loop_id is not None and cache.get(STEAM_REFRESH_GUARD_KEY) != loop_id: # Another loop owns the guard (or it expired), stop this one return try: refresh_steam_games(game_ids) finally: if loop_id is not None: cache.set(STEAM_REFRESH_GUARD_KEY, loop_id, timeout=_guard_timeout()) refresh_steam_games_task.using( run_after=timezone.now() + steam_refresh_interval() ).enqueue(loop_id=loop_id)