fix: #748 — fastify wait_for_promise condvar wait + Rejected→500 branch#782
Merged
Conversation
… cap Issue #748 root cause: in process_request (both perry-ext-fastify and perry-stdlib fastify), when a handler returns a Promise the dispatcher calls wait_for_promise() which polled `js_promise_state` 10000 times × 100us sleep (~1s budget) then bailed regardless of whether the promise settled. After the wait, `js_promise_value(ptr)` runs unconditionally — and that helper returns `(*promise).value` which stays at its initialized 0.0 for both Pending and Rejected promises. So a chain that needed >1s (rate-limiter + argon2 hash + multiple DB roundtrips in skelpo-shop-admin signup) produced the literal ASCII byte 0x30 ('0') as the response body with HTTP 200 (default `status_code` — reply.code(201) never ran). The orphaned chain was also abandoned: the dispatcher stops pumping microtasks, so every operation after the timeout silently no-op'd — explaining 'first INSERT commits, rest don't'. Fix: - Replace the polling/sleep loop with a condvar-based wait via js_wait_for_event() (the same primitive the codegen-emitted `await` body uses). Wakes the moment any stdlib worker calls js_notify_main_thread (which js_promise_resolve/reject already do). Drives timer ticks + stdlib pump + microtasks every iteration. No fixed iteration limit. - After wait_for_promise returns, check js_promise_state. If Rejected (state == 2), surface a 500 response with { error: <reason> } JSON instead of letting js_promise_value return 0.0 and serialize as the literal byte '0'. Applied to both perry-ext-fastify/src/server.rs and perry-stdlib/src/fastify/server.rs. Validated via /tmp/repro748_fastify.ts: a handler with ~1.4s of sequential setTimeout delays now returns the explicit JSON body + HTTP 201 instead of the literal '0' + HTTP 200 that the pre-fix runtime produced.
8d456db to
99a9359
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
crates/perry-ext-fastify/src/server.rs::wait_for_promise()andcrates/perry-stdlib/src/fastify/server.rs::wait_for_promise(), the dispatcher polledjs_promise_state10000 × 100 µs (≈1 s) and then returned regardless of state. After return,js_promise_value(ptr)(initialized to0.0inPromise::new) gave Rustf64 0.0for any Pending or Rejected promise. The dispatcher'sbuild_response_bodythen serialized that as the literal byte0x30("0") with the defaultstatus_code = 200— matching Native route returns body0+ HTTP 200 after sequence of @perryts/mysql writes; first INSERT commits, rest silently no-op, explicit return dropped #748's wire symptom exactly. The orphaned chain was also abandoned: once the dispatcher returned, microtasks stopped pumping, so every queued INSERT after the timeout silently no-op'd.js_wait_for_event— the same primitive the codegen-emittedawaitbody already uses. No fixed iteration limit; wakes the moment any stdlib worker callsjs_notify_main_thread. Drives timer ticks + stdlib pump + microtasks every iteration.wait_for_promisereturns, branch onjs_promise_stateand respondHTTP 500with a{ "error": <reason> }JSON envelope instead of lettingjs_promise_valuereturn0.0and masquerade as a successful response.Closes #748.
Caveat: I could not exactly reproduce the user's "first INSERT commits, rest silently no-op" symptom in a synthetic repro without
@perryts/mysqllinkage. Thewait_for_promise1 s timeout path is the only place I found wherejs_promise_value(0.0_initial)reaches the response writer, and the wire byte0x30+ HTTP 200 + dropped explicit return + abandoned subsequent awaits all match. Worth re-running skelpo-shop-admin against this build to confirm; if it still repros,PERRY_MT_PROFILE=1will narrow the stall point.Test plan
cargo build --release -p perry-ext-fastify -p perry-stdlibgreen/tmp/repro748_fastify.ts— Fastify handler with 8×300 ms sequential awaits (2.4 s total). Pre-fix: would returnHTTP 200body0once the chain exceeded 1 s. Post-fix: returns full JSON body +HTTP 201after ≈2.46 s.test_fastify_integration.tsstill passes; 10 fastify-ext unit tests + 22 fastify-stdlib unit tests all pass.test_gap_console_methods,test_gap_regexp_advanced).