Skip to content
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

Ability to call Javascript methods from Rust threads #197

Closed
jugglingcats opened this issue Mar 14, 2017 · 15 comments
Closed

Ability to call Javascript methods from Rust threads #197

jugglingcats opened this issue Mar 14, 2017 · 15 comments

Comments

@jugglingcats
Copy link

jugglingcats commented Mar 14, 2017

As discussed on Slack: https://rust-bindings.slack.com/archives/neon/p1489508576491688.

The basic idea is that a Rust addon can create a continuous background thread that notifies Javascript when things happen (event style).

Psuedo Javascript:

addin.run(function() { 
  console.log("got the event!")
}

Psuedo Rust:

fn run(call: Call) -> JsResult<JsString> {
    let scope = call.scope;
    let f = call.arguments.require(scope, 0)?.check::<JsFunction>()?;

    thread::spawn(|| {
        while (true) {
            let args: Vec<Handle<JsNumber>> = vec![];
            match f.call(scope, JsNull::new(), args) {
                _ => { println!("Call to javascript done!"); }
            }
            thread::sleep(1000);
        }
    });
    Ok(JsString::new(scope, "hello node").unwrap())
}

If this was achievable it would be a lot nicer than the C equivalent, an example of which is here: https://gist.github.com/matzoe/11082417.

I imagine there would need to be some kind of wrapper around the call, so maybe not completely transparent as shown above.

@jugglingcats
Copy link
Author

jugglingcats commented May 16, 2017

I've made a bit of progress with this but am stuck! @dherman perhaps you can help me.

I've managed to invoke a Rust function using uv_async_send (ie. on the libuv default thread). In this method/context it's safe to get the current isolate scope and invoke the Javascript callback, but I don't really understand how to encapsulate this information from the Neon Call object and make use of it.

Am struggling both with Rust basics and the idiomatic Neon way this should work. Any advice/pointers gratefully received. I can't even tell if I'm close or not!!

My working Rust project (so far) is here:
https://github.com/jugglingcats/neon-uv/tree/iter1

My working C++ project (which does invoke the js callback) is here:
https://github.com/jugglingcats/node-uv/tree/iter1

The C++ project shows the basic approach to converting a Local to Persistent in v8 in order to preserve between the main addon method and the callback, although I don't claim to understand this fully. We need to do something similar in Rust-land. C++ snippet:

	v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(args[0]);
	auto savedFunc = new v8::Persistent<v8::Function>();
	savedFunc->Reset(isolate, func);
	handle->data = savedFunc;

and (in callback):

	v8::Isolate *isolate = Isolate::GetCurrent();
	HandleScope scope(isolate);

	v8::Persistent<v8::Function> *func = (v8::Persistent<v8::Function> *) handle->data;
	func->Get(isolate)->Call(isolate->GetCurrentContext()->Global(), 0, NULL);

@jedireza
Copy link
Contributor

jedireza commented May 17, 2017

The C++ project shows the basic approach to converting a Local to Persistent in v8 in order to preserve between the main addon method and the callback [...]

IIRC this is where I left off when attempting async callbacks with Neon. We need a way to persist the callback so that Rust doesn't drop it when the call goes out of scope.

I spoke with @dherman about this (a while back) and we (at the time) thought it was best to not introduce the idea of Persistent handles in the Neon API and instead expose something more specific to async callbacks.

@jugglingcats
Copy link
Author

I agree... that's what I meant by finding an idiomatic Neon way of doing it. The Neon developer (hopefully) shouldn't concern themselves with local and persistent.

I wondered if it would be possible to make Call (or it's arguments) Send or Sync. Then they could be passed across the thread boundary. I don't know if this even makes sense as a suggestion though, or how hard it would be!

@dherman
Copy link
Collaborator

dherman commented May 17, 2017

@jugglingcats I think you have some of the right ideas, but I don't have the complete picture in my head so I can't spell out exactly what needs to be done. I can give you a few thoughts, though:

Unfortunately, it's not safe for another Rust thread to call JS on its same thread -- we can't allow that, because a) the GC isn't multithreaded and the callback closes over data from the main thread, and b) having multiple threads work on arbitrary shared data violates JS's run-to-completion semantics. (Does your C code do that, btw? It's possible I'm missing something but I'd expect that generally to crash or cause strange unspecified behavior.) The only safe thing to do, AFAIK, would be to let the Rust background thread add events onto the main thread's queue, to be dispatched on the main thread.

Similarly, it's not safe to let Call or other data structures like that implement Send or Sync, because again they are pointing to JS data from the main thread.

What I believe we want to do, at least at a hand-wavy level, is bridge between JS's concept of dynamic transferring (and maybe also structured clone) and Rust's ownership. So for example, it should be possible to transfer a typed array to a background Rust thread and then let Rust transfer it back to the callback when it's done doing the work. If we set the types up so that it's a dynamic error to try to send non-transferrable data to the background thread, then we'll be guaranteed to be thread-safe.

Another way of thinking about this is that this is kind of like web workers and postMessage, except that we can probably make the API nicer.

I'd be happy to collaborate with you on this when I have time, btw. Right now I'm trying to get the web site presentable because we're planning on doing some blog posts about using Neon with Electron and how Wire has recently put Neon into production, and I want people to have a good landing experience. After that I have a couple of hacking projects on various parts of Neon that I want to do, but having more powerful ways to do background computation in Rust is a high-priority use case.

@jugglingcats
Copy link
Author

jugglingcats commented May 18, 2017

Hi @dherman, agree mostly with your analysis. The postMessage analogy is not quite correct though IMO, because with postMessage you are limited strictly to transferring data, whereas within a node addon, you can pass a Javascript callback (function) around. You cannot execute the callback in another thread, but you can pass it around and provided you marshall onto the main node (libuv) thread you can call it later.

The key to passing the callback (and any other node data) around is converting from Local to Persistent. I think this is to avoid the possibility that node's GC cleans up the data after the initial addon function returns. It keeps it in memory until you're done with it.

There are two methods of marshalling onto the main node thread that I'm aware of, with slightly different semantics:

uv_queue_work
This is what you most commonly see used. In this case your request goes to the node thread pool. You pass two native callbacks: one for the work itself, and one that's invoked after the work is complete. The worker function is executed in the background and the 'complete' function is called on the main thread (ie. can call a Javascript callback, if appropriate).

This is convenient for ad-hoc long running native tasks because node does the thread pool management.

uv_async_send
This simply queues a native callback. In this case your callback runs some time later in the main node thread. Anything done in the native callback blocks the main thread, but it's good if you have your own background thread and just want to notify out via a Javascript callback. This is my use case. I guess it's a bit like setImmediate in Javascript-land.

The C++ example I posted shows the mechanism for uv_async_send, but doesn't actually do any threading. But the structure is correct regarding Local and Persistent handling.

Now for my bit of hand waving...

It seems to me that Rust should have a way to mutate data when sending it across threads. I don't understand the mechanics of this, but something Sync or Send should be able to say "hey, I see I'm being transferred across thread boundary, I need to do xxxxx". If this were possible, you could encapsulate the switch from Local to Persistent in this mechanism, perhaps in the Neon Handle code. Then instead of trying to move the whole Call across, you could pick out any parameters like the Javascript callback and transfer that across. Basically anything that's a Handle.

Appreciate you are busy with other activity. This isn't super-urgent for us: we don't need this feature right away. But am interested to see how it progresses -- I think it could work very sweetly.

@Qard
Copy link

Qard commented Jun 5, 2017

Should be possible to contain the response data in a movable object with the callback and just hand it to libuv to execute in the uv_default_loop() thread.

@jugglingcats
Copy link
Author

Can you elaborate a little? It is the javascript function handle that needs to be kept around, to be invoked later during the async send native callback (on main thread)

@dherman
Copy link
Collaborator

dherman commented Jun 5, 2017

@jugglingcats Are you on the slack today? I'd be happy to chat -- I've been looking into this issue lately.

@king6cong
Copy link
Contributor

Any updates? @jugglingcats @dherman continuous calls into a javascript function from Rust is a feature I looking forward to for a long time :)

@Acanguven
Copy link

As #375 merged, what is the roadmap ahead for full implementation?

@kjvalencik
Copy link
Member

@Acanguven The feature is currently not stabalized. Currently there is a limitation to the API that may require a breaking change. Specifically, it is not possible to handle Javascript exceptions in the scheduled Rust callback.

One proposal is the implementation of a try/catch feature in Neon, allowing the schedule_with API to remain un-changed. However, the higher level schedule API may be changed to more closely mirror the Task API.

Lastly, the schedule API passes a &mut Context where most neon APIs move ownership of the Context. This is also being evaluated.

Thanks!

@tom-haines
Copy link

I have an example I've worked through today based on the test cases for the
neon::event::EventHandler (via the event-handler-api).
If useful, could push this to neon examples repo ?

@ghost
Copy link

ghost commented Jul 7, 2020

@tom-haines can you point me towards your repo?
Also is there any future plans for this feature

@Brooooooklyn
Copy link

Brooooooklyn commented Jul 7, 2020

@kjvalencik
Copy link
Member

kjvalencik commented Sep 15, 2021

This has been implemented with Channel and JoinHandle. neon-bindings/rfcs#32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants