From 2656376cd5a55c3a6c1047734c8e1b7eb0bb8f21 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Wed, 19 Aug 2026 07:29:26 +0200 Subject: [PATCH] fix(compile): shield the sync endpoint from client disconnects nginx 504s a cold ESP-IDF build long before it finishes; Starlette then cancels the handler and the cancellation killed the build subprocess MID-WRITE, leaving truncated .obj files in the persistent per-target build cache - every later build of that target failed with 'ranlib: file truncated' until the cache was wiped (bitten twice on staging the day the P4 lane landed). asyncio.shield lets the build run to completion with a consistent cache; only the response is lost, and the async /start + /status flow remains the right path for long builds. --- backend/app/api/routes/compile.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/app/api/routes/compile.py b/backend/app/api/routes/compile.py index ca07fd8c..26c9263a 100644 --- a/backend/app/api/routes/compile.py +++ b/backend/app/api/routes/compile.py @@ -617,7 +617,18 @@ async def compile_sketch( files = _resolve_files(request) started = time.monotonic() try: - response = await _run_compile(request, files, requester_id=user_id) + # Shielded: when the client (or a proxy timeout - nginx 504s a cold + # ESP-IDF build well before it finishes) drops the connection, + # Starlette cancels this handler. Without the shield the cancellation + # killed the build subprocess MID-WRITE and left truncated .obj files + # in the persistent per-target build cache, poisoning every LATER + # build of that target ("ranlib: file truncated", seen twice on + # staging the day the P4 lane landed). The shield lets the build run + # to completion and keep the cache consistent; only the response is + # lost. + response = await asyncio.shield( + asyncio.ensure_future(_run_compile(request, files, requester_id=user_id)) + ) except Exception as e: await record_compile( user_id=user_id,