Mqx
Mqx2w ago

How does FFI work under the hood when using nonblocking?

How does the nonblocking functionality of the ffi interface work under the hood when using nonblocking functions. In node for every new async function call, a new thread is spawned. How does that work in Deno?
3 Replies
AapoAlas
AapoAlas2w ago
IIRC it uses the tokio thread pool, so it won't be one thread per one call. The exact code for doing nonblocking FFI calls is this: https://github.com/denoland/deno/blob/main/ext/ffi/call.rs#L373, so it's using spawn_blocking from deno_core.
Mqx
MqxOP7d ago
Thanks 👍🏻 The reason why I am asking is, I am currently arguing with a colleague about the usage of the nonblocking: true from the ffi interface. He said that technically using it would be wrong, because I am trying to make a C++ function asynchronous that is isn’t async. I have a SerialRead function in my C++ code that blocks and I want to make it nonblocking for the TS context. He is saying that technically I should use a Worker or build my own async wrapper in TS around that. I said that this is what the nonblocking flag of the ffi interface is for.
AapoAlas
AapoAlas7d ago
Yeah; as long as the C++ API is safe to call from foreign threads then the nonblocking flag is absolutely viable here. Its specific intent is to be used as a "light-weight native code Worker" kind of thing. But! If you're constantly calling that same API (or set of APIs) in an "internally synchronous" loop and only sending messages out of the loop occasionally, eg.
while(true) {
await lib.nonblockingA();
await lib.nonblockingB();
await lib.nonblockingC();
sendMessageAboutOneLoopFinishing();
}
while(true) {
await lib.nonblockingA();
await lib.nonblockingB();
await lib.nonblockingC();
sendMessageAboutOneLoopFinishing();
}
then it probably would be more efficient to spawn a dedicated Worker that just does:
while(true) {
lib.blockingA();
lib.blockingB();
lib.blockingC();
self.postMessage("oneLoopFinished");
}
while(true) {
lib.blockingA();
lib.blockingB();
lib.blockingC();
self.postMessage("oneLoopFinished");
}
Since the A/B/C methods don't interleave, creating an intervening Promise for each and going into the event loop is kind of wasted (if there is no other work to be done in the event loop). Moving to a dedicated Worker means that you have one core fully dedicated to just running the native code and your main thread will just receive occasional messages from there.

Did you find this page helpful?