Skip to content

fix(backend-native): convert .unwrap() panic in stream chunk transform to UserError via reject#10882

Draft
tlangton3 wants to merge 1 commit into
cube-js:masterfrom
tlangton3:cube/fix-backend-native-stream-chunk-transform-panic
Draft

fix(backend-native): convert .unwrap() panic in stream chunk transform to UserError via reject#10882
tlangton3 wants to merge 1 commit into
cube-js:masterfrom
tlangton3:cube/fix-backend-native-stream-chunk-transform-panic

Conversation

@tlangton3
Copy link
Copy Markdown
Contributor

Summary

Replaces an unguarded .unwrap() in the JS-Rust streaming bridge (js_stream_push_chunk) with a reject()-based error flow.

Found while investigating #10875 (the actual fix for that is in #10877). Well-behaved drivers don't currently hit this, but if a driver pushes a malformed chunk shape that fails JsValueObject::get, the .unwrap() panics inside Neon. Depending on JS state, this can leak an unhandled rejection on Node's tick queue.

Changes

Replaced the .unwrap() with an error arm that:

  • Calls JsWriteStream::reject(err_msg) to surface the error through the Rust mpsc channel to the wire layer.
  • Resolves the JS per-chunk callback via wait_for_future_and_execute_callback to prevent the Node side from hanging. Both notifications are required here to satisfy both ends of the FFI bridge.

Testing

Added FakeMalformedRowStream to test/response-fake.ts to push a chunk with invalid nested arrays. The new test (does not crash node when a stream chunk fails to transform) verifies that the underlying error reaches the wire cleanly without triggering Neon's panic-recovery wrapper.

…m to UserError via reject

`js_stream_push_chunk` in the JS-Rust streaming bridge previously
called `.unwrap()` on the result of `transform_response`. If the chunk
pushed from JS contained a value of an unexpected shape (e.g. a JS
array where a primitive was expected), `transform_response` returned
`Err` and `.unwrap()` panicked inside Neon's bridge context — Neon
recovers the panic by surfacing it as `"internal error in Neon
module: called Result::unwrap()..."` rather than the underlying error
message, and depending on the surrounding JS state can leak an
unhandled rejection on Node's tick queue.

This is distinct from cube-js#10875's BigQuery production crash (which fires
upstream of `js_stream_push_chunk` and is fixed in a sibling commit
to `BigQueryDriver.stream`). It is, however, a separate latent panic
vector across the FFI boundary that's worth hardening — the kind of
"theoretical bomb" the next driver to mis-shape a chunk would
detonate.

Replaces the `.unwrap()` with a match arm that:
  - calls `JsWriteStream::reject(err_msg)` so the error flows through
    the existing mpsc channel and the wire layer can emit a structured
    Postgres `ErrorResponse`,
  - routes the JS-side per-chunk callback through the same
    `wait_for_future_and_execute_callback` wrapper used on the success
    path so async-callback timing is consistent (no Zalgo).

Adds a regression test (`does not crash node when a stream chunk
fails to transform`) using a synthetic `FakeMalformedRowStream` that
pushes a chunk whose field values are nested arrays — guaranteed to
fail `JsValueObject::get` and exercise the new error arm. Verified
the test fails when the fix is reverted (Neon's panic-wrapper string
appears in the wire output) and passes with the fix (the underlying
transform error reaches the client cleanly).
@github-actions github-actions Bot added rust Pull requests that update Rust code javascript Pull requests that update Javascript code pr:community Contribution from Cube.js community members. labels May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code pr:community Contribution from Cube.js community members. rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant