From 7e239f162942059a66a705a6af0720778262704b Mon Sep 17 00:00:00 2001 From: BiFangKNT <1320414964@qq.com> Date: Tue, 15 Sep 2026 20:30:34 +0800 Subject: [PATCH] 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 --- src/langbot/pkg/api/http/service/maintenance.py | 14 +++++++++----- src/langbot/pkg/storage/media.py | 5 ++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/api/http/service/maintenance.py b/src/langbot/pkg/api/http/service/maintenance.py index 562c17d61..3ba4e77c5 100644 --- a/src/langbot/pkg/api/http/service/maintenance.py +++ b/src/langbot/pkg/api/http/service/maintenance.py @@ -91,25 +91,29 @@ class MaintenanceService: 0, '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 = ( - await self.ap.storage_mgr.media_cache.cleanup( + await media_cache.cleanup( media_retention_days, 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 {} ) - return { + result = { 'uploaded_files': await self._cleanup_expired_uploaded_files(context, upload_retention_days), 'log_files': await asyncio.to_thread( self._cleanup_expired_log_files, log_retention_days, ) - if await self._is_oss_singleton(context) + if is_singleton 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]: require_workspace_uuid(context) diff --git a/src/langbot/pkg/storage/media.py b/src/langbot/pkg/storage/media.py index 0029013ff..8f5537fb3 100644 --- a/src/langbot/pkg/storage/media.py +++ b/src/langbot/pkg/storage/media.py @@ -120,6 +120,7 @@ class MediaCache: try: await asyncio.to_thread(os.utime, full_path, (now, now)) except Exception: + # Failures to update mtime are intentionally ignored because LRU touch is opportunistic. pass async def cleanup( @@ -166,6 +167,7 @@ class MediaCache: expired_deleted += 1 bytes_freed += stat.st_size except OSError: + # Best-effort cleanup; file may already be gone or temporarily inaccessible. pass else: remaining.append((entry, stat.st_size, stat.st_mtime)) @@ -184,7 +186,8 @@ class MediaCache: bytes_freed += size total_bytes -= size except OSError: - pass + # Best-effort cleanup; file may already be gone or temporarily inaccessible. + continue return { 'expired_deleted': expired_deleted,