feat: bound bulk notification fan-out (#145)
CI / lint (pull_request) Successful in 9s
CI / build-frontend (pull_request) Successful in 4s

This commit is contained in:
timmy committed 2026-08-06 22:52:48 +00:00
1 parent 955343a428
commit 36d7fb9ce8
2 files changed
+110 -6

No files matched your search

+32 -6
View File
@@ -53,6 +53,8 @@ EVENT_STREAM_TIMEOUT_SECONDS = 5.0
READINESS_TIMEOUT_SECONDS = 5.0
REVIEW_DETAIL_TIMEOUT_SECONDS = 5.0
NOTIFICATION_MUTATION_TIMEOUT_SECONDS = 5.0
BULK_NOTIFICATION_CONCURRENCY = 5
BULK_NOTIFICATION_DEADLINE_SECONDS = 6.0
LIVE_SNAPSHOT_FRESHNESS_SECONDS = 8.0
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
_live_snapshot_task: asyncio.Task | None = None
@@ -444,13 +446,37 @@ async def _mark_notification_read_result(thread_id: int) -> tuple[int, bool]:
@app.patch("/api/v1/notifications/read")
async def read_notifications(batch: NotificationReadBatch) -> JSONResponse:
thread_ids = list(dict.fromkeys(batch.ids))
results = await asyncio.gather(
*(_mark_notification_read_result(thread_id) for thread_id in thread_ids)
semaphore = asyncio.Semaphore(BULK_NOTIFICATION_CONCURRENCY)
async def mark_within_limit(thread_id: int) -> tuple[int, bool]:
async with semaphore:
return await _mark_notification_read_result(thread_id)
tasks = [asyncio.create_task(mark_within_limit(thread_id)) for thread_id in thread_ids]
done, pending = await asyncio.wait(
tasks, timeout=BULK_NOTIFICATION_DEADLINE_SECONDS
)
for task in pending:
task.cancel()
if pending:
await asyncio.gather(*pending, return_exceptions=True)
succeeded_ids = {
thread_id
for task in done
if not task.cancelled() and task.exception() is None
for thread_id, succeeded in [task.result()]
if succeeded
}
failed = [thread_id for thread_id in thread_ids if thread_id not in succeeded_ids]
return JSONResponse(
{
"marked": [thread_id for thread_id in thread_ids if thread_id in succeeded_ids],
"failed": failed,
},
headers={"Retry-After": "1"} if failed else None,
)
return JSONResponse({
"marked": [thread_id for thread_id, succeeded in results if succeeded],
"failed": [thread_id for thread_id, succeeded in results if not succeeded],
})
@app.patch("/api/v1/notifications/{thread_id}/read")