fix(maintenance): guard media cache cleanup and annotate exception suppression

- Safely resolve media_cache via getattr in cleanup_expired_files to prevent AttributeError when storage_mgr is unset in cloud/test fixtures
- Only attach 'media_files' in cleanup return dictionary when media cache is active on singleton, preserving exact return contract for cloud maintenance tests
- Add explanatory comments to exception suppression in MediaCache.touch and cleanup loops to address code-quality review findings
This commit is contained in:
BiFangKNT
2026-09-15 20:30:34 +08:00
parent c16cd433e0
commit 7e239f1629
2 changed files with 13 additions and 6 deletions
@@ -91,25 +91,29 @@ class MaintenanceService:
0, 0,
'storage.media_cache.max_size_mb', 'storage.media_cache.max_size_mb',
) )
media_cache = getattr(getattr(self.ap, 'storage_mgr', None), 'media_cache', None)
is_singleton = await self._is_oss_singleton(context)
media_cleanup = ( media_cleanup = (
await self.ap.storage_mgr.media_cache.cleanup( await media_cache.cleanup(
media_retention_days, media_retention_days,
media_max_size_mb, media_max_size_mb,
) )
if hasattr(self.ap.storage_mgr, 'media_cache') and await self._is_oss_singleton(context) if media_cache is not None and is_singleton
else {} else {}
) )
return { result = {
'uploaded_files': await self._cleanup_expired_uploaded_files(context, upload_retention_days), 'uploaded_files': await self._cleanup_expired_uploaded_files(context, upload_retention_days),
'log_files': await asyncio.to_thread( 'log_files': await asyncio.to_thread(
self._cleanup_expired_log_files, self._cleanup_expired_log_files,
log_retention_days, log_retention_days,
) )
if await self._is_oss_singleton(context) if is_singleton
else 0, else 0,
'media_files': media_cleanup.get('expired_deleted', 0) + media_cleanup.get('size_deleted', 0),
} }
if media_cache is not None and is_singleton:
result['media_files'] = media_cleanup.get('expired_deleted', 0) + media_cleanup.get('size_deleted', 0)
return result
async def get_storage_analysis(self, context: TenantContext) -> dict[str, Any]: async def get_storage_analysis(self, context: TenantContext) -> dict[str, Any]:
require_workspace_uuid(context) require_workspace_uuid(context)
+4 -1
View File
@@ -120,6 +120,7 @@ class MediaCache:
try: try:
await asyncio.to_thread(os.utime, full_path, (now, now)) await asyncio.to_thread(os.utime, full_path, (now, now))
except Exception: except Exception:
# Failures to update mtime are intentionally ignored because LRU touch is opportunistic.
pass pass
async def cleanup( async def cleanup(
@@ -166,6 +167,7 @@ class MediaCache:
expired_deleted += 1 expired_deleted += 1
bytes_freed += stat.st_size bytes_freed += stat.st_size
except OSError: except OSError:
# Best-effort cleanup; file may already be gone or temporarily inaccessible.
pass pass
else: else:
remaining.append((entry, stat.st_size, stat.st_mtime)) remaining.append((entry, stat.st_size, stat.st_mtime))
@@ -184,7 +186,8 @@ class MediaCache:
bytes_freed += size bytes_freed += size
total_bytes -= size total_bytes -= size
except OSError: except OSError:
pass # Best-effort cleanup; file may already be gone or temporarily inaccessible.
continue
return { return {
'expired_deleted': expired_deleted, 'expired_deleted': expired_deleted,