Skip to content

fix: #748 — fastify wait_for_promise condvar wait + Rejected→500 branch#782

Merged
proggeramlug merged 1 commit into
mainfrom
fix/748-fastify-wait-promise
May 15, 2026
Merged

fix: #748 — fastify wait_for_promise condvar wait + Rejected→500 branch#782
proggeramlug merged 1 commit into
mainfrom
fix/748-fastify-wait-promise

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

  • In both crates/perry-ext-fastify/src/server.rs::wait_for_promise() and crates/perry-stdlib/src/fastify/server.rs::wait_for_promise(), the dispatcher polled js_promise_state 10000 × 100 µs (≈1 s) and then returned regardless of state. After return, js_promise_value(ptr) (initialized to 0.0 in Promise::new) gave Rust f64 0.0 for any Pending or Rejected promise. The dispatcher's build_response_body then serialized that as the literal byte 0x30 ("0") with the default status_code = 200 — matching Native route returns body 0 + 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.
  • Replaces the polling/sleep loop with a condvar-based wait via js_wait_for_event — the same primitive the codegen-emitted await body already uses. No fixed iteration limit; wakes the moment any stdlib worker calls js_notify_main_thread. Drives timer ticks + stdlib pump + microtasks every iteration.
  • Adds a Rejected branch: after wait_for_promise returns, branch on js_promise_state and respond HTTP 500 with a { "error": <reason> } JSON envelope instead of letting js_promise_value return 0.0 and 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/mysql linkage. The wait_for_promise 1 s timeout path is the only place I found where js_promise_value(0.0_initial) reaches the response writer, and the wire byte 0x30 + 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=1 will narrow the stall point.

Test plan

  • cargo build --release -p perry-ext-fastify -p perry-stdlib green
  • /tmp/repro748_fastify.ts — Fastify handler with 8×300 ms sequential awaits (2.4 s total). Pre-fix: would return HTTP 200 body 0 once the chain exceeded 1 s. Post-fix: returns full JSON body + HTTP 201 after ≈2.46 s.
  • Existing test_fastify_integration.ts still passes; 10 fastify-ext unit tests + 22 fastify-stdlib unit tests all pass.
  • Gap suite unchanged: 33 pass / 2 pre-existing failures (test_gap_console_methods, test_gap_regexp_advanced).

… 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.
@proggeramlug proggeramlug force-pushed the fix/748-fastify-wait-promise branch from 8d456db to 99a9359 Compare May 15, 2026 07:06
@proggeramlug proggeramlug merged commit 97114fa into main May 15, 2026
9 checks passed
@proggeramlug proggeramlug deleted the fix/748-fastify-wait-promise branch May 15, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native route returns body 0 + HTTP 200 after sequence of @perryts/mysql writes; first INSERT commits, rest silently no-op, explicit return dropped

1 participant