Prevent concurrent rescans with async lock

- Add _scan_lock asyncio.Lock to AnimeService
- Check if lock is held before starting rescan
- Use async with to ensure lock is released on completion or exception
- All 1024 tests passing
This commit is contained in:
Lukas 2025-12-24 21:10:19 +01:00
parent 19cb8c11a0
commit b6d44ca7d8
3 changed files with 65 additions and 28 deletions

View File

@ -17,7 +17,7 @@
"keep_days": 30 "keep_days": 30
}, },
"other": { "other": {
"master_password_hash": "$pbkdf2-sha256$29000$NkbIuZfSGiMEAGDMWau1tg$pMLf3tp4uH36YeG3KBgkX3F00dwbN2dGPQCWNvrjhnU", "master_password_hash": "$pbkdf2-sha256$29000$1TrHeM8Z45yTsjZG6B1DKA$w4AoTXhgcGh90quXesvoRqVkH720fYXEu8LI2L4nUFM",
"anime_directory": "/mnt/server/serien/Serien/" "anime_directory": "/mnt/server/serien/Serien/"
}, },
"version": "1.0.0" "version": "1.0.0"

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"master_password_hash": "$pbkdf2-sha256$29000$tTYmJCSEUArhnLN2TmktpQ$mhMKshEetPJfjRqYYUUvOGNRxcnIMNIto73IRKw4hPM",
"anime_directory": "/mnt/server/serien/Serien/"
},
"version": "1.0.0"
}

View File

@ -56,6 +56,8 @@ class AnimeService:
self._scan_total_items: int = 0 self._scan_total_items: int = 0
self._is_scanning: bool = False self._is_scanning: bool = False
self._scan_current_directory: str = "" self._scan_current_directory: str = ""
# Lock to prevent concurrent rescans
self._scan_lock = asyncio.Lock()
# Subscribe to SeriesApp events # Subscribe to SeriesApp events
# Note: Events library uses assignment (=), not += operator # Note: Events library uses assignment (=), not += operator
try: try:
@ -482,36 +484,47 @@ class AnimeService:
All series are identified by their 'key' (provider identifier), All series are identified by their 'key' (provider identifier),
with 'folder' stored as metadata. with 'folder' stored as metadata.
Note:
Only one scan can run at a time. If a scan is already in
progress, this method returns immediately without starting
a new scan.
""" """
try: # Check if a scan is already running (non-blocking)
# Store event loop for event handlers if self._scan_lock.locked():
self._event_loop = asyncio.get_running_loop() logger.info("Rescan already in progress, ignoring request")
logger.info( return
"Rescan started, event loop stored",
loop_id=id(self._event_loop), async with self._scan_lock:
series_app_id=id(self._app),
scan_handler=str(self._app.scan_status),
)
# SeriesApp.rescan returns scanned series list
scanned_series = await self._app.rescan()
# Persist scan results to database
if scanned_series:
await self._save_scan_results_to_db(scanned_series)
# Reload series from database to ensure consistency
await self._load_series_from_db()
# invalidate cache
try: try:
self._cached_list_missing.cache_clear() # Store event loop for event handlers
except Exception: # pylint: disable=broad-except self._event_loop = asyncio.get_running_loop()
pass logger.info(
"Rescan started, event loop stored",
loop_id=id(self._event_loop),
series_app_id=id(self._app),
scan_handler=str(self._app.scan_status),
)
# SeriesApp.rescan returns scanned series list
scanned_series = await self._app.rescan()
# Persist scan results to database
if scanned_series:
await self._save_scan_results_to_db(scanned_series)
# Reload series from database to ensure consistency
await self._load_series_from_db()
except Exception as exc: # pylint: disable=broad-except # invalidate cache
logger.exception("rescan failed") try:
raise AnimeServiceError("Rescan failed") from exc self._cached_list_missing.cache_clear()
except Exception: # pylint: disable=broad-except
pass
except Exception as exc: # pylint: disable=broad-except
logger.exception("rescan failed")
raise AnimeServiceError("Rescan failed") from exc
async def _save_scan_results_to_db(self, series_list: list) -> int: async def _save_scan_results_to_db(self, series_list: list) -> int:
""" """