-
Notifications
You must be signed in to change notification settings - Fork 27
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
improve negotiation flushing #52
Conversation
First, this change exposes a Flush function so users can flush a handshake without writing or reading. We need this in libp2p to flush before closing for writing. Second, this change flushes on Close. Otherwise, we could end up with the following situation: 1. A: Send data. 2. B: Receive data. 3. B: Close the stream. (no flush) 4. A: Wait for an EOF from the stream (ensure receipt of data). 5. B: ERROR: the stream was closed but no multistream header was sent.
a49f9df
to
5d5e851
Compare
// Multistream represents in essense a ReadWriteCloser, or a single | ||
// communication wire which supports multiple streams on it. Each | ||
// stream is identified by a protocol tag. | ||
type Multistream interface { |
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.
Replacing this with an explicit LazyConn
is technically breaking, but nothing uses it (and the interface was quite useless...).
multistream.go
Outdated
@@ -201,7 +209,7 @@ func (msm *MultistreamMuxer) findHandler(proto string) *Handler { | |||
// a multistream, the protocol used, the handler and an error. It is lazy | |||
// because the write-handshake is performed on a subroutine, allowing this | |||
// to return before that handshake is completed. | |||
func (msm *MultistreamMuxer) NegotiateLazy(rwc io.ReadWriteCloser) (io.ReadWriteCloser, string, HandlerFunc, error) { | |||
func (msm *MultistreamMuxer) NegotiateLazy(rwc io.ReadWriteCloser) (LazyConn, string, HandlerFunc, error) { |
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.
This is breaking, and it's a problem. Currently, go-libp2p-core expects this to return an io.ReadWriteCloser
.
However, there is no explicit dependency relationship between this module and go-libp2p-core (intentionally).
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, I've reverted this. I'm going to do type assertions in go-libp2p where necessary.
Unfortunately, go-libp2p relies on this interface (for now).
ca2b5f7
to
4e08419
Compare
A few thoughts:
|
The goal of the "lazy" handshake is actually:
In general, I don't expect users to open streams until they actually need to send data, so I'm not so concerned about always handshaking. We could walk away if we've never used the stream (never including never reading), but I'm not sure if this is worth the complexity for such an edge case.
This is protected by a |
I generally agree. Although usually we just close and flush async. The guarantee is: if you want to make sure the other side receives everything, call |
@Stebalien In (2b), I mean that the ongoing "read handshake" races with https://github.com/multiformats/go-multistream/blob/master/lazyClient.go#L75 |
If you want to stick to this convention (which also seems fine), is there anything preventing you from doing it simply, e.g. like: func (l *lazyClientConn) Close() error {
go func() {
_ = l.Flush()
l.werr = l.con.Close() // sync this write somehow
}()
return nil
} |
There's no reason we absolutely can't do it I just think the current solution is the lesser of two evils. |
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.
Left a comment, this makes sense to me. Although my familiarity with this part of the code base and it's rough edges isn't as good as I'd like it to be.
@raulk any thoughts before @Stebalien starts moving this along through the stack?
@Stebalien do you mind if while we propagate these changes through go-libp2p (and go-ipfs) we use stacks of PRs instead of merging + releasing at each step? It'll give us the most test coverage since we have test coverage throughout the stack,
First, this change exposes a Flush function so users can flush a handshake without writing or reading. We need this in libp2p to flush before closing for writing.
Second, this change flushes on Close. We can drop the read half of the handshake, but we need to send the write half. Otherwise, we could end up with the following situation:
This is slightly unfortunate as Close should ideally never block. But close is allowed to flush and the alternative is to spawn a goroutine.