-
Notifications
You must be signed in to change notification settings - Fork 25
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
Run handlers in a different thread than io #23
base: master
Are you sure you want to change the base?
Conversation
Can this handle multiple levels of nesting, like a stack of 5 calls in interleaved directions? The pynvim client runs each handler in a lightweight thread (greenlet) to support these kinds of situations. It might be worthwhile to use one rust async task per handler once those become available, though that would require another set of API wrappers, I suppose. |
Not fully. Responses are handled all the time. Requests and notifications are handled sequentially, with the handler thread blocking while a request/notification is handled. So if you have a request/notification, and the handler can only finish if another request/notification has been handled, you've got yourself a deadlock again (not sure if the terminology is right). Sounds like something one wouldn't do anyways, maybe? Could you point out the corresponding code in the python client? Seem like we really want that once async/await becomes available in rust... |
c3f50a6
to
2c11eb8
Compare
Hello! Line 20 in 4fd95d2
|
Somewhat, the I spoke to soon. Whatever we do in any handler, the way an Two thoughts (e: Still sort of valid, but ignore them until we've sorted out the above problem):
|
src/rpc/client.rs
Outdated
handler.handle_notify(&method, params); | ||
} | ||
Ok(model::RpcMessage::RpcResponse { .. }) => { | ||
error!("Handler threat does not handle responses!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo? "Handler thread..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup :D
@KillTheMule do you have a minimal viable example demonstrating the deadlock you're trying to avoid? |
No, I don't, my own plugin shows this, but it's not really minimal. It's not hard to see though, just imagine a Just the same, imagina a plugin that calls |
So vim sends a request to the rust plugin, and the rust plugin tries to call I think I can tinker with that to understand the situation better. Thanks. |
You could take scorched earth, take, e.g. your handler for (e) Ah no that doesn't work, because it's "just" a notification, so the io thread does not wait for a result. |
Is there a way forward with this? I'm using this on a vendored copy of neovim-lib for my plugin without issues (not a huge usage scenario, granted). I'd be putting in more work if needed, but I'd need a bit of guidance if/how it should be done. Or is there something unclear? Should I put work into producing a MWE? |
Sorry for long response. |
No worries :)
I understand. Otoh, we're already using a different thread for the iO, so maybe it's not that bad.
So like, pass a buffer to the handler, have the hander pass that buffer to the main thread and return immediately, then the request gets handled without blocking the iO thread... but how's the data written into the buffer returned to neovim? I don't fully understand how to do this, but it gave me an idea: How about we pass a closure to the handler, that the handler than invokes on the let writer = &mut *writer.lock().unwrap();
model::encode(writer, response).expect("Error sending RPC response"); Seems doable, I'll try to get that to work over the weekend!
I also put the notification handler on the other thread on the offchance someone write a notification handler that tried to get data from nvim before returning control. Might not even be possible...
I don't understand this, sorry. Overall though, this might be doable with async/await, once that lands on stable, but I'm not sure right now how or if it's a good idea. |
What if, instead of requiring the API user to dispatch the IO thread through |
Ok, here's a different angle, having the handler take a closure argument where it's supposed to stick in the return value or a possible error. I'm not sure it's optimal (using @vhakulinen I don't think exposing things this low-level is appropriate, it would put quite a bit more burden on plugin authors. Plus, I'm not really sure why everyone wants to avoid threads so much. You don't want to pile them up all that much, alright, but it's one of the nice features of rust that one can use threads easily, so let take advantage of that :) |
Hey!
I've run into a race condition with neovim-lib: If we recieve a
Request
, then the handler for that request blocks the iO thread (I'm calling it the iO thread, it's the one spawned bydispatch_thread
). That's a problem for 2 related reasons:Both times, a timeout is the result. I've run into 2. because the request during a test would arrive faster than my main thread could reach it's event loop, and when it requested the current buffer to set up things, it would never get an answer because the request was already blocking the iO thread.
So here's a rough shot at fixing this. The iO thread sends Requests/Notifications to another thread, which handels those, while the iO thread stays free to get more events, in particular responses. The new thread needs to take ownership of the closed variables (notably, the handler itself), which implied that we need to change the signature of the handler functions to take
String
instead of&str
. It's not a problem per se (we weren't doing anything with the method name anyways other than passing it to the handler), but it's a breaking change.Also, I've opted to saving the new threads handle in the
dispatch_guard
. I've also not fully polished that, let me know what you think and I'll clean up theunwrap
s.CC @bfredl @vhakulinen