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

Worker Creation from Foreign Thread #669

Open
computerquip-streamlabs opened this issue May 6, 2017 · 1 comment
Open

Worker Creation from Foreign Thread #669

computerquip-streamlabs opened this issue May 6, 2017 · 1 comment

Comments

@computerquip-streamlabs
Copy link

computerquip-streamlabs commented May 6, 2017

AsyncWorker is useful whenever we want to have an asynchronous call to foreign code and potentially that foreign code can call back into the V8 main thread using AsyncProgressWorker.

However, what if you have a callback from a foreign thread that you don't control? In this case, AsyncWorker cannot be created from within the foreign thread (or it would not be trivial in best case) since you don't have an active V8 isolate. You can create one but anything that you pass to that worker from the main isolate would be useless in the new isolate without heavy amounts of context copying (of which I'm not sure can be done correctly right now). So using a V8 callback from the main isolate is pointless here.

Also, in order to call a V8 callback in the first place, memory must be copied again and sent down the queue again via Send member function. This seems a tad wasteful when I really just want a function to execute in the main thread to begin with.

In the end, I determined I needed a new wrapper to achieve what I wanted and created something for my personal project which basically just creates an uv_async_t. I make a wrapper function where whenever I get the signal from a native thread, it creates a new object with the data provided by the signal and sends it off. It calls an Execute function, which runs in the main thread to which I can call my JS callbacks from, no need for a middle man. The memory is marshaled so the Execute function cleans up its own object at the very last second.

TL;DR I'm curious as to why nobody else seems to have run into this problem and/or if there's a better solution that I'm not seeing. The solution I made seems pretty solid but I'm wondering if it's okay to destroy uv_async_t at the last second like I'm doing since while I don't see anywhere that it does and it seems to work, I'm afraid this may change later on or might be platform specific.

In addition, I tried using uv_queue_work with uv_default_loop but this didn't execute on the main thread. However, uv_async_send does, while also using uv_default_loop. Why is this?

@mkrufky
Copy link
Collaborator

mkrufky commented May 11, 2017

Starting with your last question, uv_queue_work should only be called from the main thread, it will queue a job to run in one of uv's worker threads, and the job's callback will run in the main thread. uv_async_send , however, can be called in any thread and will queue a job to wake and run in the main thread. This is described in much better detail in the libuv documentation. Does this answer that part of your question?

As for a wrapper for worker creation from a foreign thread, all of the advice that I have read so far recommends to use AsyncProgressWorker for this, since it uses uv_async_send for its async worker functionality. By the time I had discovered nan I had already solved this in a different way:

I needed this exact functionality in a project I worked on a few years ago, but back then it only needed to work with node 0.10.x and node 0.12.x, and we were not using nan at all in that project. I solved my need by creating a singleton class that contained a uv_async_t as a class member (which I've since learned is a bad idea, since a singleton static object will be destroyed after v8 is torn down, which is a big no-no, but it seemed to work nicely for us at the time) . The foreign thread creates a new object for data / state storage, stores a reference (or pointer) to it in a container of some sort (std::vector was good for our purposes back then, but you may want to use a queue or list or some other container) ... when the main thread wakes up (keep in mind that libuv will coalesce calls to uv_async_send, also described in better detail here) the main thread iterates through the entire container (list / vector / queue / etc) to process all of the incoming data. This eliminates all the copies that you described.

I don't think there's anything wrong with the method that you described, although it might be a bit expensive if you need to create and destroy a uv_async_t for every event (not to mention all the copies). It might be better for you to use a single uv handle for this and collect references to your event data in a container like I described above. Don't forget to free that memory after processing...

As for "why nobody else seems to have run into this problem?" ... I find myself asking the same question. I intended on experimenting more with the AsyncProgressWorker to understand better how this can be used to solve this specific use case, but I'm not working on the project that needed this functionality anymore, so I never got around to that kind of experimenting while using nan. If AsyncProgressWorker doesn't do it for you and you end up wanting to move forward with your own wrapper class, please post it so others can see -- I would be happy to resurrect some of my old code and see if it works for me.

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

2 participants