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

core/muxing: Flatten StreamMuxer interface to poll_{inbound,outbound,address_change,close} #2724

Merged
merged 26 commits into from
Jul 18, 2022

Conversation

thomaseizinger
Copy link
Contributor

@thomaseizinger thomaseizinger commented Jun 22, 2022

Description

This is the next changeset on our journey to #2722!

Instead of having a mix of poll_event, poll_outbound and poll_close, we flatten the entire interface of StreamMuxer into 4 individual functions:

  • poll_inbound
  • poll_outbound
  • poll_address_change
  • poll_close

This design is closer to the design of other async traits like AsyncRead and AsyncWrite. It also allows us to delete the StreamMuxerEvent.

Links to any relevant issues

Part of #2722.

Open Questions

Change checklist

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • A changelog entry has been made in the appropriate crates

@thomaseizinger thomaseizinger changed the title Remove dedicated "outbound" functions from StreamMuxer. core/muxing: Remove dedicated "outbound" functions from StreamMuxer Jun 22, 2022
@thomaseizinger
Copy link
Contributor Author

Rebased to stay on top of #2710.

@mxinden
Copy link
Member

mxinden commented Jun 24, 2022

Appreciate the elaborate pull request!

Still have to give this more thought.

As you mentioned in #2648, given that we don't have a muxer with backpressure on stream creation today (e.g. QUIC), we will likely not do a good job designing this abstraction at this point in time.

For now, we could get rid of poll_outbound for now, returning an ID on open_outbound, returning the actual outbound stream through the poll_event method.

Again, have to think about it some more.

@thomaseizinger
Copy link
Contributor Author

For now, we could get rid of poll_outbound for now, returning an ID on open_outbound, returning the actual outbound stream through the poll_event method.

If somehow possible, I'd like to get rid of this ID because it is actually unnecessarily complicated. Without this ID, we can drop abstractions like OutboundSubstreams from #2648.

There is nothing special about a substream that requires us to do this explicit book-keeping. All we need is a mechanism for the caller to say: I want a new one.

I started with a simple poll_outbound: bool flag on the poll function but that felt like an odd API.

Base automatically changed from refactor/remove-io-error-bound to master June 24, 2022 06:26
This variant is not yet constructed but we will need it very soon.
It is easier for clients to only call one poll function that can
be configured, which substreams to open. This API change is also
trying to plan ahead for muxers like QUIC which actually allow
to only open substreams in one direction.
@thomaseizinger
Copy link
Contributor Author

thomaseizinger commented Jun 24, 2022

Clean rebase onto master, no changes other than dropping commits of previous parent PR.

@thomaseizinger thomaseizinger marked this pull request as ready for review June 24, 2022 07:48
@thomaseizinger
Copy link
Contributor Author

I just had what I think is a really good idea!

Instead of having a StreamMuxerEvent, we can have one poll function per concern. Not only is this more consistent with the existing poll_close, this is also how traits like AsyncRead and AsyncWrite are designed.

@@ -1,13 +1,14 @@
# 0.34.0 - unreleased

- Introduce `StreamMuxerEvent::map_inbound_stream`. See [PR 2691].
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 deleted this changelog entry because it is not released yet and it no longer makes any sense if the entire type is gone now!

@thomaseizinger
Copy link
Contributor Author

Instead of having a StreamMuxerEvent, we can have one poll function per concern. Not only is this more consistent with the existing poll_close, this is also how traits like AsyncRead and AsyncWrite are designed.

This is a cool idea and might even give downstream users of StreamMuxer the ability to backpressure incoming streams.

Yep that is what I thought too! Exposing individual knobs has a lot more flexibility.

I do want to try and build a wrapper around this with different trade-offs, i.e. "simpler API, less knobs".

Sorry for the delay here. I will take a more in-depth look.

No rush. I am away from my laptop for the next week or so anyway :)

muxers/yamux/src/lib.rs Outdated Show resolved Hide resolved
handler: wrapped_handler,
open_info: VecDeque::with_capacity(8),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This replaces a SmallVec from Muxing to ensure that we use the OpenInfo in a FIFO manner. A capacity of 8 at least makes only one allocation and thus, the (unbenchmarked) performance profile should be similar.

Open to better ideas.

Comment on lines 174 to 183
if let Poll::Ready(substream) = self.muxing.poll_inbound(cx)? {
self.handler
.inject_substream(substream, SubstreamEndpoint::Listener);
continue;
}

if let Poll::Ready(address) = self.muxing.poll_address_change(cx)? {
self.handler.inject_address_change(&address);
return Poll::Ready(Ok(Event::AddressChange(address)));
}
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 think it makes sense to flip these.

swarm/src/connection.rs Outdated Show resolved Hide resolved
@thomaseizinger
Copy link
Contributor Author

Instead of having a StreamMuxerEvent, we can have one poll function per concern. Not only is this more consistent with the existing poll_close, this is also how traits like AsyncRead and AsyncWrite are designed.

This is a cool idea and might even give downstream users of StreamMuxer the ability to backpressure incoming streams. Sorry for the delay here. I will take a more in-depth look.

Did you get a chance to look at this @mxinden ?

@mxinden
Copy link
Member

mxinden commented Jul 7, 2022

Instead of having a StreamMuxerEvent, we can have one poll function per concern. Not only is this more consistent with the existing poll_close, this is also how traits like AsyncRead and AsyncWrite are designed.

This is a cool idea and might even give downstream users of StreamMuxer the ability to backpressure incoming streams. Sorry for the delay here. I will take a more in-depth look.

Did you get a chance to look at this @mxinden ?

I think we should do this change 👍 As far as I can tell, the QUIC implementation would be easy to adapt:

https://github.com/libp2p/rust-libp2p/pull/2289/files#diff-d59efbc1f8dc6ed1521dd36e906216f4ff4ade561dca9b50beea1da354240cf7

Can we prevent users (not implementors) forgetting to call one of the poll_* methods. Would #[must_use] prevent that? If not, I guess given that libp2p-swarm is the main user, I think it is fine.

I suggest we wait for #2289 and #2622 to adapt to the latest StreamMuxer changes before merging here. That way we can better predict the impact on these implementations. @thomaseizinger what do you think?

//CC @elenaf9 and @kpp for #2289 and //CC @melekes for #2622

@thomaseizinger
Copy link
Contributor Author

thomaseizinger commented Jul 7, 2022

Can we prevent users (not implementors) forgetting to call one of the poll_* methods. Would #[must_use] prevent that? If not, I guess given that libp2p-swarm is the main user, I think it is fine.

I don't think it is possible. We might be able to provide a wrapper struct but I see libp2p-core as a set of fundamental building blocks that optimizes for flexibility and thus, reading docs and proper use of the APIs is essential IMO.

@thomaseizinger
Copy link
Contributor Author

I suggest we wait for #2289 and #2622 to adapt to the latest StreamMuxer changes before merging here. That way we can better predict the impact on these implementations. @thomaseizinger what do you think?

Hmm, I'd prefer to merge this early to be honest. Happy to wait for an assessment from @kpp, @elenaf9 and @melekes whether merging first would make things easier.

@melekes
Copy link
Contributor

melekes commented Jul 12, 2022

How does the new interface translates when it comes to OutboundSubstream

    type OutboundSubstream = BoxFuture<'static, Result<Arc<DetachedDataChannel>, Self::Error>>;

In webrtc transport, I create a future in open_substream and poll it in poll_outbound. Where am I supposed to create a future now? Note the code I'm using is async.

@thomaseizinger
Copy link
Contributor Author

The new interface provides a poll_outbound function which should eventually return a Substream.

If your implementation is based on async, you'd probably want to have an Option<BoxFuture> which you fill with a new one when it is empty or poll the inner one when it is present.

@thomaseizinger
Copy link
Contributor Author

Currently, this interface still relies on internal mutability but I am planning to make it Pin<&mut Self> in a follow-up PR.

@mxinden
Copy link
Member

mxinden commented Jul 14, 2022

I suggest we wait for #2289 and #2622 to adapt to the latest StreamMuxer changes before merging here. That way we can better predict the impact on these implementations. @thomaseizinger what do you think?

Hmm, I'd prefer to merge this early to be honest. Happy to wait for an assessment from @kpp, @elenaf9 and @melekes whether merging first would make things easier.

Fine by me as well. Mind resolving the conflicts @thomaseizinger?

@thomaseizinger
Copy link
Contributor Author

Conflicts resolved. Please check if I updated the changelog and manifest versions correctly @mxinden !

@thomaseizinger
Copy link
Contributor Author

@mxinden @elenaf9 This is ready for a review again.

Copy link
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

Thanks for the thorough work! Great to have you here.

Poll::Ready(SubstreamEvent::AddressChange(address)) => {
self.handler.inject_address_change(&address);
return Poll::Ready(Ok(Event::AddressChange(address)));
continue; // Go back to the top, handler can potentially make progress again.
Copy link
Member

@mxinden mxinden Jul 18, 2022

Choose a reason for hiding this comment

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

Side note, I like this style of:

  • When one made progress
  • but one does not return
  • start from the top via continue as other sub components might be able to make progress now.

Copy link
Contributor Author

@thomaseizinger thomaseizinger Jul 18, 2022

Choose a reason for hiding this comment

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

I do too although it is a pattern I had to get used to and it is not trivial to understand when you get started with async Rust.

@mxinden mxinden merged commit 1a553db into master Jul 18, 2022
mxinden added a commit to mxinden/rust-libp2p that referenced this pull request Jul 19, 2022
Follow up on libp2p#2724. Given that
libp2p-core is bumped to v0.35.0, libp2p-tcp needs to be bumped as well.
mxinden added a commit that referenced this pull request Jul 19, 2022
Follow up on #2724. Given that
libp2p-core is bumped to v0.35.0, libp2p-tcp needs to be bumped as well.
@thomaseizinger thomaseizinger deleted the refactor/remove-poll-outbound branch August 2, 2022 10:16
elenaf9 added a commit to elenaf9/rust-libp2p that referenced this pull request Aug 3, 2022
umgefahren pushed a commit to umgefahren/rust-libp2p that referenced this pull request Mar 8, 2024
…nd,address_change,close}` (libp2p#2724)

Instead of having a mix of `poll_event`, `poll_outbound` and `poll_close`, we
flatten the entire interface of `StreamMuxer` into 4 individual functions:

- `poll_inbound`
- `poll_outbound`
- `poll_address_change`
- `poll_close`

This design is closer to the design of other async traits like `AsyncRead` and
`AsyncWrite`. It also allows us to delete the `StreamMuxerEvent`.
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.

3 participants