fix(runtime-sdk): retain the ASGI finalizer proxy until completion - #249
fix(runtime-sdk): retain the ASGI finalizer proxy until completion#249audreyfeldroy wants to merge 1 commit into
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
| finalizer = run_in_background(finalize_request()) | ||
| finalizer_proxy = create_proxy(finalizer) | ||
| finalizer.add_done_callback(lambda finished: finalizer_proxy.destroy()) | ||
| wait_until(finalizer_proxy) |
There was a problem hiding this comment.
I'm confused about this patch because wait_until() should already keep the PyProxy alive until the promise resolves. See here:
https://github.com/cloudflare/workerd/blob/main/src/pyodide/python-entrypoint-helper.ts#L58-L70
There was a problem hiding this comment.
It definitely fails without this change though.
There was a problem hiding this comment.
Oh right the test monkeypatches wait_until.
|
Closing as not valid in my opinion. |
|
Could you post a reproducer for your problem? Perhaps there's a different problem here to solve. This link is broken though: |
|
Here’s the standalone reproducer you requested, attached below. Thanks for catching the problem with the earlier test. This reproducer calls the real runtime APIs. With workers-runtime-sdk 1.8.4 and Workerd 1.20260815.1:
All three return HTTP 200, and each case runs in a fresh Worker instance. The ZIP includes pinned dependencies, instructions, a verification script, and captured results. Could the module-level export be missing the proxy retention provided by |
|
Thanks for the reproducer. So:
That is, this fails: from workers import wait_until
wait_until(task)but this works: self.ctx.waitUntil(task)I will try to understand what we should do to fix this. |
|
Okay I have a fix. Will open a PR later today. Thanks again @audreyfeldroy for the reproducer! |
|
Created cloudflare/workerd#7293 with a fix. |
A streaming ASGI response can return HTTP 200 while its
waitUntiltask rejects with a destroyed borrowed-proxy error. The finalizer introduced in #229 currently passes a Python task directly to JavaScript:waitUntilretains the task after returning to Python. The implicit proxy is borrowed and expires at that return boundary, before JavaScript finishes assimilating the task. This can prevent request finalization, including background work and lifespan shutdown, from completing reliably after the response.Create an explicitly owned proxy and destroy it when the finalizer task completes, following the WebSocket task-ownership pattern already used in #166. Existing task retention, exception logging, and the finalizer's
finally: await shutdown()remain intact.The new regression runs inside Workerd. It substitutes JavaScript
Array.pushforwait_until: both retain their argument and return no Promise. It retrieves and awaits that retained task afterfetch()returns, rather than checking only the response body. It also verifies the complete streamed body and background work preceding lifespan shutdown. This catches the rejected task that an otherwise successful response can hide.Related work: #229 introduced this HTTP finalizer; #166 provides the existing ownership pattern. The open worker-lifetime lifespan proposal #239 does not address this proxy lifetime.
Test Plan
pyodide.ffi.JsExceptionreporting the destroyed borrowed proxy when awaiting the retained finalizer.git diff --checkpassed.Local harness details: macOS, Node 24.19.0, uv 0.12.3, Workerd 1.20260815.1. The host harness used that existing pinned Workerd binary instead of its unpinned
npm i workerdstep. Python dependencies were constrained to releases before September 2 for the local test setup.The wider local compatibility run could not execute Python 3.12 (Pyodide interpreter query failed during setup with
ModuleNotFoundError: No module named 'python') or Python 3.14 (the installed Workerd does not recognizepython_workers_314). These are unverified configurations, not passing checks. The new regression is included in the existing ASGI suite for the normal CI matrix.