Skip to content

Commit

Permalink
fix(backend-native): Handle closed channel on Rust side (#9242)
Browse files Browse the repository at this point in the history
DataFusion can execute complex queries, containing multiple CubeScans, specifically multiple `CubeScanExecutionPlan` in physical plan
`CubeScanExecutionPlan`s can be executed concurrently, and as such concurrently trigger load calls on `TransportService`
Then, if one of several concurrent calls receives error whole stream with still-executing-plans can be dropped
For `NodeBridgeTransport` this would lead to dropping receiver side in `call_raw_js_with_channel_as_callback`
But sender side would continue to live in JS thread
Then JS side can call `writerOrChannel.resolve`, and it would find that channel is closed, and raise exception
But then this exception will be caught, leading to `writerOrChannel.reject` call
And because `writerOrChannel.resolve` already consumed channel it would raise its own exception
And, because `wrapNativeFunctionWithStream` uses async function, this exception would lead to promise rejection, will not get caught in native part on JS side (it does not care for return values), and can trigger unhandled rejection
  • Loading branch information
mcheshkov authored Feb 20, 2025
1 parent 511ca8a commit 1203291
Showing 1 changed file with 17 additions and 1 deletion.
18 changes: 17 additions & 1 deletion packages/cubejs-backend-native/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,23 @@ function wrapNativeFunctionWithStream(
if (!!response && !!response.stream) {
response.stream.destroy(e);
}
writerOrChannel.reject(errorString(e));

try {
writerOrChannel.reject(errorString(e));
} catch (rejectError) {
// This is async function, just for usability, it's return value is not expected anywhere
// Rust part does not care for returned promises, so we should take care here to avoid unhandled rejections
// `writerOrChannel.reject` can throw when channel is already dropped by Rust side
// This can happen directly, when drop happened between creating channel and calling `reject`,
// or indirectly, when drop happened between creating channel and calling `resolve`, resolve raised an exception
// that was caught here
// There's nothing we can do, or should do with this: there's nobody to respond to
if (process.env.CUBEJS_NATIVE_INTERNAL_DEBUG) {
console.debug('[js] writerOrChannel.reject exception', {
e: rejectError,
});
}
}
}
};
}
Expand Down

0 comments on commit 1203291

Please sign in to comment.