-
Notifications
You must be signed in to change notification settings - Fork 939
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
Questions on futures/async/await roadmap #993
Comments
Hey 👋
I agree with that feeling. However "could probably use some iteration" is easier said than done 😅
Not any time soon. We've put a lot of efforts to not use
It is certain that async/await will greatly improve things, and probably divide by 2 the number of lines of code of this library (and same for
That is in fact already the case. The node handlers and protocols handlers run on separate tasks. When they emit events, they are propagated to the "main" task that only runs the network behaviour.
cc #746 |
Ahh I seem to have misunderstood. Thanks for pointing that out! I also have some additional follow-up questions: Some of us are (understandably) worried about pulling in a core dependency without any of us being core contributors. If we were to pull in rust-libp2p, we would likely invest heavily in it, but some of us are concerned about commit rights and the pull request review process. I'm curious what your thoughts are on commit rights etc? |
That's quite a broad question. EDIT: I originally put my opinion here, but I guess it doesn't really matter. Right now we're merging changes quite quickly in this repo because all the main contributors are within Parity and we can discuss changes in real life in our office. But if more people start opening pull requests, we'll probably be more cautious when it comes to the merging speed. I don't know what the policy of Protocol Labs (the owners of the libp2p organization) when it comes to giving merge rights is. cc @mgoelzer @raulk |
Building on what @tomaka is saying: The current governance model of Maybe you and your group, @tomaka, myself and @raulk should talk more about where you're going and what you want to contribute? You can email me at mike@libp2p.io, or ping @mgoelzer in https://riot.im/app/#/room/#libp2p:matrix.org. Email might be easier to do a long discussion over time. Excited that you're interested and hope to chat further @phlip9. |
The issue went a bit off-topic, as it was originally about async/await. Let's recenter. Since futures will likely get stabilized in Rust 1.35, we should consider transitioning to standard-library-futures (also known as futures 0.3 and futures-preview) as a first step. The blocker on this is that we either need tokio to transition to new futures, or a stable way to turn old futures into new futures. Considering that the standard-library doesn't have an equivalent to The various inherent |
Futures 0.3 are stabilized in the current nightly and will be in stable 1.35. Something to note from the conversation, boxing futures 0.3 is only necessary if they require pinning, and that only occurs due to holding references across yield/await points. Since futures won't initially have async/await, boxing is not necessary except to get a trait object, which would be the case with 0.1 anyways. |
The
Again,
I won't argue this as I agree that returning futures is probably better than providing explicit poll functions, but this is why
Are you expecting me to give something from [EDIT] 5 hours before I posted this, |
the new futures just landed on stable rust: https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html |
Some random thoughts: Pass the executor by parameterThe executor is no longer a thread-local thingy and should instead be passed explicitly. Protocol upgradesIt should be possible to write something like: let upgrade = upgrade::from_fn("/foo/1.0.0", async |stream| {
let handshake_msg = read_one(&stream).await?;
check(&handshake_msg)?;
Ok(stream)
}); And the returned object could implement Node handlerThe We could similarly do something like that: let handler = handler::from_fn(async |context| {
let substream = context.open_substream().await;
loop {
let in_event = context.next_event().await;
context.generate_event(..).await;
}
}); And same for the |
Even though I opened #1196, we might not switch to new futures before async/await just yet? The parts that use tokio-codec, copying between read and write, and sink need to be rewritten. EDIT: of course if someone else wants to do it, you're welcome! But it's a lot of efforts and many parts of it might be deleted next month when async/await are stable. |
I tried playing a bit with async/await, and we can probably reduce by two thirds some elements of the code of rust-libp2p. Here's an example diff: diff --git a/core/src/upgrade/apply.rs b/core/src/upgrade/apply.rs
index d9a3e7a1..787debdf 100644
--- a/core/src/upgrade/apply.rs
+++ b/core/src/upgrade/apply.rs
@@ -41,16 +41,15 @@ where
}
/// Tries to perform an upgrade on an inbound connection or substream.
-pub fn apply_inbound<C, U>(conn: C, up: U) -> InboundUpgradeApply<C, U>
+pub async fn apply_inbound<C, U>(conn: C, up: U) -> Result<U::Output, UpgradeError<U::Error>>
where
C: AsyncRead + AsyncWrite,
U: InboundUpgrade<C>,
{
let iter = UpgradeInfoIterWrap(up);
- let future = multistream_select::listener_select_proto(conn, iter);
- InboundUpgradeApply {
- inner: InboundUpgradeApplyState::Init { future }
- }
+ let future = multistream_select::listener_select_proto(conn, iter).compat();
+ let (info, connection, upgrade) = future.await?;
+ Ok(upgrade.0.upgrade_inbound(connection, info.0).compat().await.map_err(UpgradeError::Apply)?)
}
/// Tries to perform an upgrade on an outbound connection or substream.
@@ -66,75 +65,6 @@ where
}
}
-/// Future returned by `apply_inbound`. Drives the upgrade process.
-pub struct InboundUpgradeApply<C, U>
-where
- C: AsyncRead + AsyncWrite,
- U: InboundUpgrade<C>
-{
- inner: InboundUpgradeApplyState<C, U>
-}
-
-enum InboundUpgradeApplyState<C, U>
-where
- C: AsyncRead + AsyncWrite,
- U: InboundUpgrade<C>
-{
- Init {
- future: ListenerSelectFuture<C, UpgradeInfoIterWrap<U>, NameWrap<U::Info>>,
- },
- Upgrade {
- future: U::Future
- },
- Undefined
-}
-
-impl<C, U> Future for InboundUpgradeApply<C, U>
-where
- C: AsyncRead + AsyncWrite,
- U: InboundUpgrade<C>,
-{
- type Item = U::Output;
- type Error = UpgradeError<U::Error>;
-
- fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
- loop {
- match mem::replace(&mut self.inner, InboundUpgradeApplyState::Undefined) {
- InboundUpgradeApplyState::Init { mut future } => {
- let (info, connection, upgrade) = match future.poll()? {
- Async::Ready(x) => x,
- Async::NotReady => {
- self.inner = InboundUpgradeApplyState::Init { future };
- return Ok(Async::NotReady)
- }
- };
- self.inner = InboundUpgradeApplyState::Upgrade {
- future: upgrade.0.upgrade_inbound(connection, info.0)
- };
- }
- InboundUpgradeApplyState::Upgrade { mut future } => {
- match future.poll() {
- Ok(Async::NotReady) => {
- self.inner = InboundUpgradeApplyState::Upgrade { future };
- return Ok(Async::NotReady)
- }
- Ok(Async::Ready(x)) => {
- debug!("Successfully applied negotiated protocol");
- return Ok(Async::Ready(x))
- }
- Err(e) => {
- debug!("Failed to apply negotiated protocol");
- return Err(UpgradeError::Apply(e))
- }
- }
- }
- InboundUpgradeApplyState::Undefined =>
- panic!("InboundUpgradeApplyState::poll called after completion")
- }
- }
- }
-}
-
/// Future returned by `apply_outbound`. Drives the upgrade process.
pub struct OutboundUpgradeApply<C, U>
where |
Some updating, for people who may be following this issue: we now have a branch named |
#1328 has been merged. Let's close this. |
Hey guys! I'm looking to use
rust-libp2p
in a project and have some observations + questions.First, some initial observations after playing around with it for a few weeks:
rust-libp2p
and see its potential, particularly if we can improve the same underlying framework and then easily share protocols built using the framework.rust-libp2p
, I find the "low-level" components, specificallyTransport
,Upgrade
, andStreamMuxer
, most appealing. The composability, "swap-ability", etc is nice.Swarm
,NetworkBehaviour
, andProtocolsHandler
though the actual API is a bit clunky and could probably use some iteration. Manually implementing state machines inNetworkBehaviour
orProtocolsHandler
using thepoll
API seems overly mechanical and error-prone.Next, some questions about direction:
Box
indirection andcompat()
wrapping, we could probably begin an incremental, piece-wise migration sooner rather than later.Ping
'sOutboundUpgrade
using async/await:NetworkBehaviour
/ProtocolsHandler
hierarchy by "hard" channel boundaries, like even closer to an actor-ish model? Instead of owning your sub-behaviour directly, you hold an in-event sender to push events into it and an out-event receiver to pull events it generates. Likewise for your parentNetworkBehaviour
orSwarm
. I think (?) this might make the async/await transition easier? Your asyncpoll
equivalent wouldselect!
across the receivers in a loop and handle events that way. The tricky part would be figuring out how to ergonomically send events on the senders if they are bounded queues. Each behaviour would get spawned as its own task on the executor. With the current approach, my uninformed and not-data-driven worry is that having one giantTask
encapsulate the wholeNetworkBehaviour
hierarchy might be too coarse-grained.Some of these points could probably live in their own issue. Look forward to hearing your thoughts.
Thanks!
-Philip
The text was updated successfully, but these errors were encountered: