-
Notifications
You must be signed in to change notification settings - Fork 43
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
Refactor Connection
to a synchronous state machine
#142
Merged
Merged
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
9bd4a34
Fix clippy warnings
thomaseizinger 9af35b8
Remove unnecessary `Option`
thomaseizinger 54296c5
Make `gargabe_collect` not async
thomaseizinger 6234e6f
Handle socket closing outside of `on_control_command`
thomaseizinger d5d5ef4
Handle GoAway logic outside of `on_stream_command`
thomaseizinger 6a55933
Remove `async` from a bunch of functions
thomaseizinger c1e805d
Introduce `Connection::poll` function
thomaseizinger e02bdbc
Minimise diff
thomaseizinger 3a6a374
Split `on_control_command`
thomaseizinger 5e64609
Use whitespace
thomaseizinger 3817398
Split `on_stream_command`
thomaseizinger 0ebd9cc
Prioritise control and stream commands over reading the socket
thomaseizinger b37e484
Fail if we want to double close a connection
thomaseizinger f776a43
Introduce internal `ConnectionState` enum
thomaseizinger d63cde2
Track closed state in connection state enum
thomaseizinger a1db18e
Inline `on_close_connection`
thomaseizinger fd886ee
Implement shutdown procedurally
thomaseizinger 527012d
Don't pause `control_receiver`
thomaseizinger f9d3f4b
Implement `ConnectionState::poll`
thomaseizinger 2df23f0
Implement `into_stream` by directly calling `poll_next`
thomaseizinger 6ca0d1f
Improve naming of `poll` fn on `ConnectionState`
thomaseizinger bb96cd9
Improve docs
thomaseizinger ff27080
Don't fail if connection got closed gracefully
thomaseizinger 527df19
Rename `Failing` to `Cleanup`
thomaseizinger 19ee6e8
Add docs to state variants
thomaseizinger 370f5bc
Track reply sender outside of `close` function
thomaseizinger 7dcecde
Handle `ControlCommand` outside of `ConnectionState::poll`
thomaseizinger fe7d000
Introduce `Frame::close_stream` ctor
thomaseizinger 2bae656
Move `Drop` impl from `Active` to `Connection`
thomaseizinger 47c684b
Implement connection closing as manual state machine
thomaseizinger 8279c27
Implement connection cleanup as manual state machine
thomaseizinger ce57e25
Don't require `T` to be `Send + 'static'`
thomaseizinger 0ac90a0
Rewrite `Control` to be a layer on top of `Connection`
thomaseizinger 79c1479
Make `control` as sister module of `connection`
thomaseizinger 3941cdc
Add test for poll-based API
thomaseizinger a0ba23b
Create workspace
thomaseizinger 654e38a
Remove comment and improve variable naming
thomaseizinger 1525cf8
Fix typo
thomaseizinger 27ed7ac
Match exhaustively
thomaseizinger 1ca32e6
Use doc link
thomaseizinger 63938b1
Bump version and add changelog entry
thomaseizinger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use crate::connection::Result; | ||
use crate::connection::{ControlCommand, StreamCommand}; | ||
use crate::frame::Frame; | ||
use crate::{frame, ConnectionError}; | ||
use futures::channel::mpsc; | ||
use futures::stream::Fuse; | ||
use futures::{ready, AsyncRead, AsyncWrite, SinkExt, StreamExt}; | ||
use std::collections::VecDeque; | ||
use std::future::Future; | ||
use std::pin::Pin; | ||
use std::task::{Context, Poll}; | ||
|
||
/// A [`Future`] that gracefully closes the yamux connection. | ||
#[must_use] | ||
pub struct Closing<T> { | ||
state: State, | ||
control_receiver: mpsc::Receiver<ControlCommand>, | ||
stream_receiver: mpsc::Receiver<StreamCommand>, | ||
pending_frames: VecDeque<Frame<()>>, | ||
socket: Fuse<frame::Io<T>>, | ||
} | ||
|
||
impl<T> Closing<T> | ||
where | ||
T: AsyncRead + AsyncWrite + Unpin, | ||
{ | ||
pub(crate) fn new( | ||
control_receiver: mpsc::Receiver<ControlCommand>, | ||
stream_receiver: mpsc::Receiver<StreamCommand>, | ||
pending_frames: VecDeque<Frame<()>>, | ||
socket: Fuse<frame::Io<T>>, | ||
) -> Self { | ||
Self { | ||
state: State::ClosingControlReceiver, | ||
control_receiver, | ||
stream_receiver, | ||
pending_frames, | ||
socket, | ||
} | ||
} | ||
} | ||
|
||
impl<T> Future for Closing<T> | ||
where | ||
T: AsyncRead + AsyncWrite + Unpin, | ||
{ | ||
type Output = Result<()>; | ||
|
||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||
let mut this = self.get_mut(); | ||
|
||
loop { | ||
match this.state { | ||
State::ClosingControlReceiver => { | ||
this.control_receiver.close(); | ||
this.state = State::DrainingControlReceiver; | ||
} | ||
State::DrainingControlReceiver => { | ||
match ready!(this.control_receiver.poll_next_unpin(cx)) { | ||
Some(ControlCommand::OpenStream(reply)) => { | ||
let _ = reply.send(Err(ConnectionError::Closed)); | ||
} | ||
Some(ControlCommand::CloseConnection(reply)) => { | ||
let _ = reply.send(()); | ||
} | ||
None => this.state = State::ClosingStreamReceiver, | ||
} | ||
} | ||
State::ClosingStreamReceiver => { | ||
this.stream_receiver.close(); | ||
this.state = State::DrainingStreamReceiver; | ||
} | ||
|
||
State::DrainingStreamReceiver => { | ||
this.stream_receiver.close(); | ||
|
||
match ready!(this.stream_receiver.poll_next_unpin(cx)) { | ||
Some(StreamCommand::SendFrame(frame)) => { | ||
this.pending_frames.push_back(frame.into()) | ||
} | ||
Some(StreamCommand::CloseStream { id, ack }) => this | ||
.pending_frames | ||
.push_back(Frame::close_stream(id, ack).into()), | ||
None => this.state = State::SendingTermFrame, | ||
} | ||
} | ||
State::SendingTermFrame => { | ||
this.pending_frames.push_back(Frame::term().into()); | ||
this.state = State::FlushingPendingFrames; | ||
} | ||
State::FlushingPendingFrames => { | ||
ready!(this.socket.poll_ready_unpin(cx))?; | ||
|
||
match this.pending_frames.pop_front() { | ||
Some(frame) => this.socket.start_send_unpin(frame)?, | ||
None => this.state = State::ClosingSocket, | ||
} | ||
} | ||
State::ClosingSocket => { | ||
ready!(this.socket.poll_close_unpin(cx))?; | ||
|
||
return Poll::Ready(Ok(())); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
pub enum State { | ||
ClosingControlReceiver, | ||
DrainingControlReceiver, | ||
ClosingStreamReceiver, | ||
DrainingStreamReceiver, | ||
SendingTermFrame, | ||
FlushingPendingFrames, | ||
ClosingSocket, | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Question not suggestion: Why deliberately implement this as a state machine instead of a procedural
async/await
?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.
So that it can be named without boxing it up.
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.
What would be the drawback of boxing it?
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.
Send
bound. In the current design, we get to delete theYamuxLocal
stuff inrust-libp2p
because theSend
bound is inferred.I don't feel strongly about either but it felt like a nice improvement as I went along. Once we get "impl Trait in type-alias" in Rust at least the boxing would go away.
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.
Is there any benchmark proving this? Is performance relevant when closing a connection?
The infectious-send-when-boxing problem is reason enough to not box in my eyes 👍