-
Notifications
You must be signed in to change notification settings - Fork 976
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
protocols/kad/: Replace manual procedural state machines with async/await #3130
Comments
Please assign it to me @mxinden . I’ll look into it! |
For reference, this is only possible for outbound streams. Inbound streams need to be able to be suspended to allow the behaviour to answer the incoming request. |
Allowing suspension is the sole point of |
If Rust would have native generators, then yeah we would likely be able to use that and resume the suspension with arbitrary data. Until then we are stuck with channels or hand-rolled state machines. |
Well, Rust has native generators, they’re just exposed without the |
Please post here once you started working on it. I will then assign you. Until then, in case someone else races you to it, great. We will have it done sooner and we will find another cool task for you. Sounds good? |
Yeah, I am familiar with genawaiter. It is a nice library :) It would be interesting to toy around with it to build something like I think the former is more verbose (need to model the data request and response) but it can potentially yield more up-to-date results for the remote. In theory, it might also suffer from higher latency because you can't enforce how quickly the data request is answered. Gathering the data ahead of time (like I do in #2852) has guaranteed latency properties, is simpler to write but can potentially serve slightly stale data, depending on how much work the protocol does between setting up the stream and when it uses the gathered data / when it would suspend waiting for more. In my personal view, good network protocol should be designed to be handled autonomously i.e. they should be quick to answer for a peer and not wait for user interaction in-between1. The design I am proposing in #2852 encourages that by making it impossible to suspend a stream to wait for more data. I think I want to write a blog post about this. Footnotes
|
I wrote up a small PoC of what I believe is a reusable abstraction that allows us to more easily express request-response protocols without having to write manual state machines every time: https://github.com/thomaseizinger/rust-async-response-stream/blob/master/tests/smoke.rs The idea is simple: It is basically a oneshot channel into a future that will send the message on the given stream. However, instead of having to construct this in every protocol, The resulting future will eventually yield the received message, a When integrating this into our codebase, I was thinking that a If this is well received, I can transfer the repository to the Input on naming, API design and overall idea welcome! |
Thanks for preparing the proof of concept.
It took me a bit to understand the internals of the abstraction. In case this turns out to not be leaky, i.e. not required to be understood by users, great. In case it is leaky, using familiar concepts like (Neat tricks with the marker types.) I am in favor of moving forward here. |
All naming subject to bike-shedding :) What in particular was hard to understand? Do you have suggestions for different names?
I think it is also worth extending the library with a I'll extend the library with a |
I have been working on solving this exact problem for a protocol I'm currently implementing with libp2p. I didn't like manual state machines either. My solution consists in the handlers and behaviors to spawn async tasks for handling requests or doing useful stuff. They then poll tasks from their own poll method. You can see how I implemented it here in the KamilataHandler struct and also in the tasks/mod.rs file. My code is still in its early days and the protocol is far from finished, but I am already very satisfied with the task logic I implemented |
Thanks for sharing! I think conceptually, this is equivalent to what is discussed in #3411 and #3130 (comment). On an abstract level, yes if you move the processing of the data into the Message passing is nice because it makes this very explicit and avoid the possibility of deadlocks. However, exercising backpressure is also really important. I am curious what the experiments by @nazar-pc will yield. |
This refactoring addresses several aspects of the current handler implementation: - Remove the manual state machine for outbound streams in favor of using `async-await`. - Use `oneshot`s to track the number of requested outbound streams - Use `futures_bounded::FuturesMap` to track the execution of a stream, thus applying a timeout to the entire request. Resolves: #3130. Related: #3268. Related: #4510. Pull-Request: #4901.
Reading and writing on inbound/outbound streams in
libp2p-kad
is implemented via hand written procedural / sequential state machines. This logic can be simplified by using Rust's async-await.Manual state machines in
libp2p-kad
:rust-libp2p/protocols/kad/src/handler.rs
Lines 140 to 163 in 43fdfe2
Example of using async-await in
libp2p-dcutr
:rust-libp2p/protocols/relay/src/v2/protocol/inbound_stop.rs
Lines 124 to 146 in 43fdfe2
The text was updated successfully, but these errors were encountered: