-
Notifications
You must be signed in to change notification settings - Fork 284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature request: Safer SysContext via thread-locals #1002
Comments
(As an aside, arguably even |
If I understand correctly, since the async code is only ever polled from the event loop (via channel), it's guaranteed that after any given await point, we are back on the main thread? If this pattern is sound, I would absolutely love to include it in Neon. This is something I wanted to include as part of Neon's async story, but couldn't quite figure out how to make it safe (I was trying to see if I could make a Putting RE: Re-entrancy. Aside signal-neon-futures is really interesting. Can you help me understand why you might want to use
Good call on |
As long as the future doesn't escape to get polled from somewhere else, yes. (Which I think FutureTask prevents, by owning the Future and only polling it with itself as the waker.) In theory this allows non-Send Futures as long as they are started from a Context rather than a Channel, and in fact I had that in a previous version, but we didn't actually use that in any way, so I took it out to simplify the code.
The fact that they said "hey, this is the pattern to use" suggests that it's not inherently a problem in Deno, but that doesn't mean it's not specifically a problem for Node. (I'm out of my depth here, I don't know the history at all. But I don't want to depend on something that might turn out to be unsafe.)
The particular concern is channel.send(|cx| {
cx.expose_to_current_thread(|| {
SysContext::with_current_context_scoped(|cx| {
let global = cx.global();
global.call_method_with(cx, "exposedToJS").exec(cx);
}
})
}
fn exposedToJS(cx: FunctionContext<'cx>) -> JsResult<JsUndefined> {
// ignore the cx we were given and…
SysContext::with_current_context_scoped(|cx| {
let global = cx.global(); // oops!
}
} But in writing all this out I realized that this can be avoided by having
Only in an async context! The point of
If we always use |
This is an interesting use case, and I agree that the scoped closure variant of My concern about using TLS would be that everyone would have to pay the runtime cost of the Neon runtime continuously updating the TLS storage, even if they're not making use of the |
Yeah, I considered having that be implicit but came to the same conclusion. That's what |
@jrose-signal Thanks for the really awesome advice to add a scoped version of As far as adding the I am interested in potentially having Neon provide a futures executor, but I would probably favor one polled by the @dherman and I took a look at One thing I might recommend in the |
I don't think this actually does anything! Either the future is polled to completion, or it's blocked. (Notice that if you submit multiple top-level Futures to run on the same Channel, they don't know anything about each other, so we can't work on independent futures in the same microtask. But that's probably a good thing for starvation reasons anyway.) |
signal-neon-futures allows using the Node event loop as an executor for Rust futures via Channels, which mostly works great. However, if we want to synchronously call back into JavaScript from within an async function, we can't: a Channel is inherently asynchronous, and a Context (correctly) has a scoped lifetime.
We're considering a workaround of putting the raw
napi_env
in a thread-local and using SysContext. Is that a pattern that's worth exposing in Neon? Something likeOne thing I'm not sure of, though, is whether this breaks under re-entrancy. That is, if JS function J1 calls Rust function R1, which saves its Context and then calls Rust function R2, which calls JS function J2, which calls Rust function R3, which looks up the thread-local context…does N-API freak out? Or do undefined things? The N-API docs say
But that doesn't really clarify nesting.
The text was updated successfully, but these errors were encountered: