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

Add ThreadSafeCallback helper to schedule callbacks on the main thread #375

Merged
merged 7 commits into from
Jan 31, 2020

Conversation

geovie
Copy link
Contributor

@geovie geovie commented Nov 29, 2018

Hi,
this adds a little helper to call a JavaScript function from any rust thread as requested in #197.
I'm not sure if this will need a RFC, but I can write one if requested.
RFC

Feedback welcome, especially about the v8 scope setup.

Inspired by mika-fischer/napi-thread-safe-callback

@maackle
Copy link

maackle commented Dec 18, 2018

I don't have the depth to comment on the viability of this, but I would love to have this functionality! I'm currently building something kind of complicated using Task because callbacks in threads are not possible.

@amilajack
Copy link
Member

@geovie sorry for the delay on this. We'll review this soon

@kjvalencik
Copy link
Member

kjvalencik commented Jan 31, 2019

Ignore my previous comment. I looked at the code and see that each is being run in a separate thread.

@qm3ster
Copy link

qm3ster commented Mar 24, 2019

Oh wow, this is exactly what I need.
I've been trying to get "emitting" to work, even with help on the JS side, but I can neither call a callback asynchronously without terminating a Task, nor pass a tokio::sync::mpsc::Receiver on the "class" struct into a new Task because of lifetimes.

@jrd-rocks
Copy link

Thanks for this. I needed some sane way to get callbacks working. This has been a life saver!

@x1957
Copy link

x1957 commented Aug 29, 2019

any updates?

@dherman
Copy link
Collaborator

dherman commented Aug 29, 2019

@x1957 I'm discussing with @aep on Slack about updating this PR, and over on the RFC PR I've asked @geovie if I can help finish up the RFC. Would love to get this landed!

@dignifiedquire
Copy link

I tried the new branch, and it breaks unfortunately, using setTimeout + a loop in the thread, seems to results in the callback from setTimeout to never be called.

@dignifiedquire
Copy link

@dignifiedquire
Copy link

sorry, it was my mistake, I was putting too much work into the cb.schedule closure, the PR in #429 does work nicely for me

crates/neon-runtime/native/src/neon.cc Outdated Show resolved Hide resolved

impl EventHandler {
pub fn new(callback: Handle<JsFunction>) -> Self {
TaskContext::with(|mut cx: TaskContext| {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is TaskContext correct here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's correct, unfortunately. In fact, this is a mistake we made in the RFC--this API is impossible because it doesn't have access to the current context.

One option is to take the context as the first argument, but then this loses most of the convenience over the bind method.

I'm thinking the simplest thing would be:

  • Remove this method
  • Rename bind back to new
  • Defer the creation of convenience methods to the future. They could potentially be built-in methods of the Context trait, like cx.event_handler(func). But there's an open question in my mind about whether you want to offer conveniences for both Rust callbacks (e.g. cx.event_handler(|cx| { ... }) and JS callbacks (e.g. cx.event_handler(func)). It seems OK to wait for some usage experience before we know exactly what convenience shorthands we want.

@geovie @kjvalencik What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's impossible, but the reference to the global context would need to be lazily created as part of the callback and not here.

With that said, I agree that it follows the existing patterns in neon better to add it to the Context trait.

I also support deferring the convenience method to a separate PR.


pub fn schedule<T, F>(&self, arg_cb: F)
where T: Value,
F: for<'a> FnOnce(&mut TaskContext<'a>) -> Vec<Handle<'a, T>>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to have IntoIterator<...> instead of Vec<..>, but I couldn't get it to work with the lifetimes....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a future improvement. I think to benefit from this, it would require a more holistic change to arguments in Neon.

let func: Handle<JsFunction> = Handle::new_internal(JsFunction::from_raw(func));
let callback: Box<F> = Box::from_raw(mem::transmute(callback));
let args = callback(&mut cx);
let _result = func.call(&mut cx, this, args);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the result (return value or exception) is discarded, I'm not sure if this is okay.
EventHandler::schedule_with was added to allow to use the result.

@geovie
Copy link
Contributor Author

geovie commented Sep 15, 2019

@dherman @kjvalencik I've updated the PR with the changes from the RFC

@kjvalencik
Copy link
Member

@geovie Are you able to update this with the latest changes from the RFC? @aep also appears to be working on this. Can you coordinate? Thanks! I would ❤️ to get this merged.

@geovie
Copy link
Contributor Author

geovie commented Sep 26, 2019

@kjvalencik The PR is already up to date with the RFC, I will rebase it later. Any review is welcome

@kjvalencik
Copy link
Member

@geovie Ah, sorry I missed that update! Thanks! I'll start reviewing.

src/eventhandler/mod.rs Outdated Show resolved Hide resolved
src/eventhandler/mod.rs Outdated Show resolved Hide resolved
src/eventhandler/mod.rs Outdated Show resolved Hide resolved
@kjvalencik
Copy link
Member

kjvalencik commented Oct 2, 2019

@geovie Unfortunately, files got moved around in #440 to prep for N-API. Are you able to resolve the conflicts? Let me know if you would like me to help.

Overall the changes look great and I'm ready to merge once the conflicts are resolved.

@geovie geovie force-pushed the feature/threadsafe_callback branch from 49b544b to 45d28f7 Compare October 2, 2019 13:18
@geovie
Copy link
Contributor Author

geovie commented Oct 2, 2019

@kjvalencik rebased on master, should be good once CI passes

Copy link
Collaborator

@dherman dherman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geovie This is truly amazing work. Thank you so much! I do have a number of suggestions for improvement, as well as a couple of tweaks to the API we came up with in the RFC discussion. But I don't think it's too far away from being ready. Feel free to ping me on Slack to discuss!

crates/neon-runtime/src/nan/threadsafecb.rs Outdated Show resolved Hide resolved
crates/neon-runtime/src/napi/threadsafecb.rs Outdated Show resolved Hide resolved
src/eventhandler/mod.rs Outdated Show resolved Hide resolved
crates/neon-sys/native/src/neon_threadsafe_cb.h Outdated Show resolved Hide resolved

impl EventHandler {
pub fn new(callback: Handle<JsFunction>) -> Self {
TaskContext::with(|mut cx: TaskContext| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's correct, unfortunately. In fact, this is a mistake we made in the RFC--this API is impossible because it doesn't have access to the current context.

One option is to take the context as the first argument, but then this loses most of the convenience over the bind method.

I'm thinking the simplest thing would be:

  • Remove this method
  • Rename bind back to new
  • Defer the creation of convenience methods to the future. They could potentially be built-in methods of the Context trait, like cx.event_handler(func). But there's an open question in my mind about whether you want to offer conveniences for both Rust callbacks (e.g. cx.event_handler(|cx| { ... }) and JS callbacks (e.g. cx.event_handler(func)). It seems OK to wait for some usage experience before we know exactly what convenience shorthands we want.

@geovie @kjvalencik What do you think?

src/eventhandler/mod.rs Outdated Show resolved Hide resolved
crates/neon-sys/native/src/neon_threadsafe_cb.h Outdated Show resolved Hide resolved
crates/neon-sys/native/src/neon_threadsafe_cb.h Outdated Show resolved Hide resolved
crates/neon-sys/native/src/neon_threadsafe_cb.h Outdated Show resolved Hide resolved
crates/neon-sys/native/src/neon_threadsafe_cb.h Outdated Show resolved Hide resolved
src/eventhandler/mod.rs Outdated Show resolved Hide resolved
@bochaco
Copy link

bochaco commented Oct 23, 2019

Thanks a lot @geovie , I guess I cannot get the value out of the cb.schedule_with call right? that's async and no way to wait for a result from it, unless perhaps using a mpsc channel? or what would you suggest for such an scenario?

@kdy1
Copy link

kdy1 commented Nov 28, 2019

When will be this feature merged?

@kdy1 kdy1 mentioned this pull request Nov 28, 2019
7 tasks
@Acanguven
Copy link

Acanguven commented Dec 5, 2019

Any updates on this? It will probably make Neon lot more useful and amazing. Because the current state of this solution limits us as we always have to use node as the data source.

Copy link
Collaborator

@dherman dherman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are so close! Thank you so much for keeping on this, @geovie. We have so many people who will benefit from this work.

We just have one last thing to change, in order to ensure we aren't creating something that we'll have to break compatibility on for the N-API transition. See the comment inline for details.

Thanks again!

@@ -522,17 +522,23 @@ extern "C" void Neon_Task_Schedule(void *task, Neon_TaskPerformCallback perform,
neon::queue_task(internal_task);
}

extern "C" void* Neon_ThreadSafeCallback_New(v8::Local<v8::Value> self, v8::Local<v8::Function> callback) {
extern "C" void* Neon_EventHandler_New(v8::Local<v8::Function> callback) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a nice idea, but unfortunately it's not future-proof for N-API, which doesn't have an equivalent of isolate::GetCurrent() and isolate->GetCurrentContext(). And in fact, this means the bind API is also not future-proof for N-API.

What this means is, we really need the EventHandler to take a Neon context as an explicit argument to its constructor(s).

So the best path forward is going to be: let's just drop this down to a single constructor, which takes three arguments: cx, self, and callback. It's a noticeably less convenient API, but I think that's OK to get started. Down the road, we can experiment with convenience methods of cx (just like the other convenience constructors such as cx.string(...) etc), like cx.event_handler(...).

But let's wait on the convenience forms and for now just have one method in the API:

EventHandler::new(cx, self, callback)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dherman Should be good now, please take a final look! 😄

@dherman
Copy link
Collaborator

dherman commented Dec 13, 2019

Any updates on this? It will probably make Neon lot more useful and amazing. Because the current state of this solution limits us as we always have to use node as the data source.

@Acanguven Thanks for keeping us on task (no pun intended) :P I've just done a review on the latest revisions. @geovie Hopefully the next round should be the last needed revision!

Copy link
Collaborator

@dherman dherman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks perfect! Thanks for the amazing work, @geovie, I think this is ready to merge.

@dherman
Copy link
Collaborator

dherman commented Dec 19, 2019

Ah, I see we're failing some Windows builds. I reran and it's failing again. Investigating...

@Others
Copy link

Others commented Dec 29, 2019

I was curious about this test failure. Here are the two interesting bits of the CI build that failed:
compile fail stuff
electron/gyp fail stuff

@dherman
Copy link
Collaborator

dherman commented Jan 24, 2020

So the compile-fail bug is probably just the usual flakiness of those tests (they're extremely sensitive to tiny changes in the Rust compiler's output formatting). I'm going to need to just disable those tests until we can come up with a better strategy for them.

From searching around, I think the electron failure is also likely unrelated to this PR: it seems like this bug. I'm looking into what we can do to fix that issue.

@dherman
Copy link
Collaborator

dherman commented Jan 27, 2020

OK I can confirm that the workaround from that issue (npm i -g npm@6.11.3) does indeed make the Electron tests pass.

This is a bit annoying! We'll need to track that node-gyp issue until they release a fix. For the time being, maybe we can get CI working by applying the workaround in the Appveyor config. I'll try a little PR to do that, which should rebase fine with this PR.

@dherman
Copy link
Collaborator

dherman commented Jan 27, 2020

Testing out a workaround: #483

@dherman
Copy link
Collaborator

dherman commented Jan 28, 2020

I got CI working again on master with #483 so I went ahead and rebased this PR -- let's see how rerunning CI goes! 🤞

@dherman
Copy link
Collaborator

dherman commented Jan 28, 2020

Doh! It looks like the changes in master did break this PR (my fault, since it took me so long to diagnose these CI issues).

@geovie Would you like me to try to fix this in your PR? (GitHub gives me permission to update your PR, but I don't want to push non-rebase changes without your permission.) I'm happy to do it unless you'd rather.

@geovie
Copy link
Contributor Author

geovie commented Jan 28, 2020

@dherman Go for it, I won't manage to do it this week...

@dherman
Copy link
Collaborator

dherman commented Jan 29, 2020

Next step to get CI working should be to conditionally replace the implementation with unimplemented!() for the N-API backend for now. I'll do that next...

@dherman dherman merged commit 03d31b6 into neon-bindings:master Jan 31, 2020
@anymos
Copy link

anymos commented Aug 26, 2020

Looking forward to see that part going live

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

Successfully merging this pull request may close these issues.