From 08ccc8b399fd53fd6c18213c0d3f3d26b0a50343 Mon Sep 17 00:00:00 2001 From: Kian Jones <11655409+kianjones9@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:04:25 -0800 Subject: [PATCH] fix: prevent db connection pool exhaustion in file status checks (#6620) Problem: When listing files with status checking enabled, the code used asyncio.gather to check and update status for all files concurrently. Each status check may update the file in the database (e.g., for timeouts or embedding completion), leading to N concurrent database connections. Example: Listing 100 files with status checking creates 100 simultaneous database update operations, exhausting the connection pool. Root cause: asyncio.gather(*[check_and_update_file_status(f) for f in files]) processes all files concurrently, each potentially creating DB updates. Solution: Check and update file status sequentially instead of concurrently. While this is slower, it prevents database connection pool exhaustion when listing many files. Changes: - apps/core/letta/services/file_manager.py: - Replaced asyncio.gather with sequential for loop - Added explanatory comment about db pool exhaustion prevention Impact: With 100 files: - Before: Up to 100 concurrent DB connections (pool exhaustion) - After: 1 DB connection at a time (no pool exhaustion) Note: This follows the same pattern as PR #6617 and #6619 which fixed similar issues in file attachment and multi-agent tool execution. --- letta/services/file_manager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/letta/services/file_manager.py b/letta/services/file_manager.py index 596b6356..d1d14d13 100644 --- a/letta/services/file_manager.py +++ b/letta/services/file_manager.py @@ -449,11 +449,15 @@ class FileManager: *[file.to_pydantic_async(include_content=include_content, strip_directory_prefix=strip_directory_prefix) for file in files] ) - # if status checking is enabled, check all files concurrently + # if status checking is enabled, check all files sequentially to avoid db pool exhaustion + # Each status check may update the file in the database, so concurrent checks with many + # files can create too many simultaneous database connections if check_status_updates: - file_metadatas = await asyncio.gather( - *[self.check_and_update_file_status(file_metadata, actor) for file_metadata in file_metadatas] - ) + updated_file_metadatas = [] + for file_metadata in file_metadatas: + updated_metadata = await self.check_and_update_file_status(file_metadata, actor) + updated_file_metadatas.append(updated_metadata) + file_metadatas = updated_file_metadatas return file_metadatas