diff --git a/portal/main.py b/portal/main.py index 60e2481..76fd283 100644 --- a/portal/main.py +++ b/portal/main.py @@ -11,6 +11,7 @@ Every page renders against the design tokens in static/tokens.css. """ from __future__ import annotations +import asyncio import logging from datetime import datetime, timezone from zoneinfo import ZoneInfo @@ -206,11 +207,13 @@ async def tool_embed(request: Request, tool_id: str) -> HTMLResponse: return templates.TemplateResponse( request, "not_found.html", {"user": user}, status_code=404 ) - # Workspace files: the user's bucket + the shared pool (does not block the - # event loop, degrades to empty list on any S3 error). + # Workspace files: the user's bucket + the shared pool (runs concurrently, + # degraded gracefully on any S3 error/timeout). user_bucket = s3_bucket_name(user.username) - my_files = await run_in_threadpool(list_recent, user_bucket, 20) - shared_files = await run_in_threadpool(list_recent, "shared-media", 20) + my_files, shared_files = await asyncio.gather( + run_in_threadpool(list_recent, user_bucket, 20), + run_in_threadpool(list_recent, "shared-media", 20), + ) ctx = { "user": user, "tool": tool, diff --git a/portal/s3.py b/portal/s3.py index a3947a4..36eb4b9 100644 --- a/portal/s3.py +++ b/portal/s3.py @@ -19,7 +19,12 @@ def s3_client(): region_name=settings.S3_REGION or None, aws_access_key_id=settings.S3_ACCESS_KEY or None, aws_secret_access_key=settings.S3_SECRET_KEY or None, - config=Config(signature_version="s3v4"), + config=Config( + signature_version="s3v4", + connect_timeout=1, + read_timeout=1, + retries={"max_attempts": 1}, + ), )