-
Notifications
You must be signed in to change notification settings - Fork 113
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
fix(server): use spawn_blocking when moving from async to sync contexts to prevent threadool deadlock #235
Conversation
543690e
to
2b96d38
Compare
…ts to prevent threadool deadlock
2b96d38
to
9b7ecb6
Compare
Checks pass. Bench on this branch vs master looks okay despite the small overhead of spawning extra resources into the secondary execution pool. Might be a small cost in the overhead but it's not large and a few runs I'm sure would show a pretty small delta in the average. I suspect ditching the mpsc would yield a better bench on this code. Will try it out. this:
master:
|
Was able to simplify/improve this a good deal. Pushed a change. Removes any dependency changes as well (preserve lock file etc.) This pr:
master:
|
1c40e2a
to
de1e0d3
Compare
The pr a bit. This is good to go IMO. I've tested this and I believe very strongly that this is the right thing to do for consumers of the api. An interesting quality in the performance is that there seem to be more consist response times (lower standard deviation). |
@@ -44,6 +44,10 @@ fn map_conversion_result<T>(res: Result<T, crate::ConversionError>) -> Result<T, | |||
res.map_err(|err| Status::invalid_argument(format!("{}", err))) | |||
} | |||
|
|||
fn map_tokio_result<T>(res: Result<T, tokio::task::JoinError>) -> Result<T, Status> { | |||
res.map_err(|err| Status::internal(format!("{}", err))) | |||
} |
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.
It looks like this is always called along with map_indradb_result
? As a result, this could be simplified so that one function handles both.
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.
nice I'll do that.
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.
K pushed a small refactor for that. I chose to keep two methods and just unwrap one into the other as the inner portion is used in send
.
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.
87ab385 - lmk if that looks okay.
Thanks for this fix! |
Issue resolved here: the Datastore's methods are sync calls from async context w/ tokio runtime.
These should be called w/ block_on, not sync, as there is IO and you want to give the option to the implementor to use async themselves. You need to spawn_blocking on these async/sync barriers!
Any place that you're handing off from the runtime into a synchronous that may potentially block, you need to throw that into spawn_blocking. If the consumers of the Datastore api would ever do any concurrent/async work that needs to block to return to the sync api, then the threadpool can deadlock (eg futures::executor::block_on) or tokio will otherwise panic (eg Handle::block_on).
This change will allow concurrency/asynchronous code to be executed within the Datastore without forcing async trait on the consumer of the api.
For context, see: tokio-rs/tokio#2376
#235 (comment)
Checks run, the benches look marginally worse due to the overhead I think, but i only ran em once and who knows.