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:
parent
19cb8c11a0
commit
b6d44ca7d8
@ -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"
|
||||||
|
|||||||
24
data/config_backups/config_backup_20251224_210928.json
Normal file
24
data/config_backups/config_backup_20251224_210928.json
Normal 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"
|
||||||
|
}
|
||||||
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user