neon-bindings / neon

Rust bindings for writing safe and fast native Node.js modules.
https://www.neon-bindings.com/
Apache License 2.0
8k stars 283 forks source link

Question: Calling JS callbacks in rayon #1006

Closed nukeop closed 10 months ago

nukeop commented 10 months ago

I'm passing a callback from JS to Rust. Then, when iterating over a collection, I want to call it with some parameters during each iteration (it's how I track progress of a long-running Rust function in JS).

However, if I do the same in a rayon ParallelIterator, understandably I'm getting this error:

`*mut napi::bindings::types::Value__` cannot be shared between threads safely
within `[closure@blahblah/lib.rs:103:14: 103:20]`, the trait `Sync` is not implemented for `*mut napi::bindings::types::Value__`
required because it appears within the type `&Handle<'_, JsFunction>`

Is there any way to do this? Any workarounds? For clarity, here's a simplified version of what I want to do:

let my_callback: Handle<JsFunction> = cx.argument(0)?;
let chars = vec!['a', 'b', 'c'];
let result: Vec<_> = chars
    .into_par_iter()
    .for_each(|| {
        my_callback(cx, cx.undefined(), cx.undefined());
    })
    .collect();

Inside the for_each, I don't want to modify any JS objects or values, I only want to call the callback I passed and JS will handle the rest.

kjvalencik commented 10 months ago

JavaScript methods/values are only accessible from the main thread. They can be held in other threads with Root, but require calling back to the main thread for use.

With your use of rayon, is the Neon function synchronous (i.e., no callback or promise)? The example seems like that's the case. If that's the case, you still need some way to synchronize back to the main thread (since multiple calls to for_each would be concurrent).

A possible option would be to create a mpsc channel where each call to for_each can send and then a single consumer outside of the parallel iterator consumes and calls the function.

It looks like the indicatif crate does something similar. https://docs.rs/indicatif/0.17.7/indicatif/trait.ParallelProgressIterator.html

nukeop commented 10 months ago

Thanks for the advice, I'll look into this. My use case is scanning a music library - Rust opens audio files, extracts metadata, and generates thumbnails for every album, and it's all synchronous. The callback is used to report progress back to JS. I'm a Rust n00b, so I didn't know about mpsc, but it looks promising.