This PR is a WIP that introduces Rust future execution managed by libuv with a working implementation.
This allows for Rust code to run concurrently on the main thread without blocking the JavaScript thread.
It's still a work in progress, requesting comments for ways I can improve the implementation if it's a good candidate for contribution.
It might be a bit confusing for consumers given the API might conflict with the tokio runtime - so there's probably a discussion to be had there.
~I only actually changed 5 files, the other changes are vendoring in libuv-rs to reuse their C bindings. In the future the relevant bindings can be brought over rather than vendoring in the whole crate.~ Published my fork of libuv for Rust to reduce the diff.
Examples
Async Function Declaration
Simple Async
Declare an async function that returns a promise and does not block the main thread.
import napi from './napi.node'
napi.foo().then(() => console.log('Hi'))
This works by essentially bolting a Rust futures executor onto Nodejs's libuv. It uses libuv hooks to trigger the Rust future executor. The futures executor runs until Rust futures are pending then yields back to nodejs's event loop.
This means that the Rust event loop will never block Nodejs's event loop and vice versa, yet both can drive their tasks forward.
I'm using the lightweight local thread executor from the futures crate.
For channels, sleep, and other common async tasks, utilities from async-std can be used.
Tokio is not suitable for this use case as it's designed to be the only executor running and cannot be driven (or at least I cannot figure out how to drive it) from an external executor.
This PR is a WIP that introduces Rust future execution managed by libuv with a working implementation. This allows for Rust code to run concurrently on the main thread without blocking the JavaScript thread.
It's still a work in progress, requesting comments for ways I can improve the implementation if it's a good candidate for contribution.
It might be a bit confusing for consumers given the API might conflict with the tokio runtime - so there's probably a discussion to be had there.
~I only actually changed 5 files, the other changes are vendoring in
libuv-rs
to reuse their C bindings. In the future the relevant bindings can be brought over rather than vendoring in the whole crate.~ Published my fork of libuv for Rust to reduce the diff.Examples
Async Function Declaration
Simple Async
Declare an async function that returns a promise and does not block the main thread.
Async MPSC Channel
This also enables the use of async channels on the main thread
Function Async Closure
You can spawn an async future within a closure using
cx.execute_async(|| async {})
.How does this work? Tokio?
This works by essentially bolting a Rust futures executor onto Nodejs's libuv. It uses libuv hooks to trigger the Rust future executor. The futures executor runs until Rust futures are pending then yields back to nodejs's event loop.
This means that the Rust event loop will never block Nodejs's event loop and vice versa, yet both can drive their tasks forward.
I'm using the lightweight local thread executor from the futures crate.
For channels, sleep, and other common async tasks, utilities from async-std can be used.
Tokio is not suitable for this use case as it's designed to be the only executor running and cannot be driven (or at least I cannot figure out how to drive it) from an external executor.
TODO: [] Tests [] Error handing [] Documentation [] Missing functionality