Closed jugglingcats closed 3 years ago
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);
The C++ project shows the basic approach to converting a
Local
toPersistent
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.
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!
@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.
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.
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.
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)
@jugglingcats Are you on the slack today? I'd be happy to chat -- I've been looking into this issue lately.
Any updates? @jugglingcats @dherman continuous calls into a javascript function from Rust is a feature I looking forward to for a long time :)
As #375 merged, what is the roadmap ahead for full implementation?
@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!
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 ?
@tom-haines can you point me towards your repo? Also is there any future plans for this feature
This has been implemented with Channel
and JoinHandle
. https://github.com/neon-bindings/rfcs/pull/32
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:
Psuedo Rust:
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.