surface connection error as connection context cancelation cause ()

* connection: surface connection error as connection context cancellation cause

* docs: add note about connection context canellation cause
This commit is contained in:
Ferdinand Holzer 2023-07-18 06:31:31 +02:00 committed by GitHub
parent ab192a084d
commit 4378283f95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 21 additions and 13 deletions

View file

@ -132,7 +132,7 @@ The `quic.Transport` contains a few configuration options that don't apply to an
#### When the remote Peer closes the Connection #### When the remote Peer closes the Connection
In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Users can use errors assertions to find out what exactly went wrong: In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Additionally, it is set as cancellation cause of the connection context. Users can use errors assertions to find out what exactly went wrong:
* `quic.VersionNegotiationError`: Happens during the handshake, if there is no overlap between our and the remote's supported QUIC versions. * `quic.VersionNegotiationError`: Happens during the handshake, if there is no overlap between our and the remote's supported QUIC versions.
* `quic.HandshakeTimeoutError`: Happens if the QUIC handshake doesn't complete within the time specified in `quic.Config.HandshakeTimeout`. * `quic.HandshakeTimeoutError`: Happens if the QUIC handshake doesn't complete within the time specified in `quic.Config.HandshakeTimeout`.

View file

@ -176,7 +176,7 @@ type connection struct {
closeChan chan closeError closeChan chan closeError
ctx context.Context ctx context.Context
ctxCancel context.CancelFunc ctxCancel context.CancelCauseFunc
handshakeCtx context.Context handshakeCtx context.Context
handshakeCtxCancel context.CancelFunc handshakeCtxCancel context.CancelFunc
@ -283,7 +283,7 @@ var newConnection = func(
connIDGenerator, connIDGenerator,
) )
s.preSetup() s.preSetup()
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), ConnectionTracingKey, tracingID)) s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler( s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
0, 0,
getMaxPacketSize(s.conn.RemoteAddr()), getMaxPacketSize(s.conn.RemoteAddr()),
@ -404,7 +404,7 @@ var newClientConnection = func(
connIDGenerator, connIDGenerator,
) )
s.preSetup() s.preSetup()
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), ConnectionTracingKey, tracingID)) s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler( s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
initialPacketNumber, initialPacketNumber,
getMaxPacketSize(s.conn.RemoteAddr()), getMaxPacketSize(s.conn.RemoteAddr()),
@ -525,7 +525,10 @@ func (s *connection) preSetup() {
// run the connection main loop // run the connection main loop
func (s *connection) run() error { func (s *connection) run() error {
defer s.ctxCancel() var closeErr closeError
defer func() {
s.ctxCancel(closeErr.err)
}()
s.timer = *newTimer() s.timer = *newTimer()
@ -552,10 +555,7 @@ func (s *connection) run() error {
} }
} }
var ( var sendQueueAvailable <-chan struct{}
closeErr closeError
sendQueueAvailable <-chan struct{}
)
runLoop: runLoop:
for { for {

View file

@ -395,6 +395,7 @@ var _ = Describe("Connection", func() {
} }
Expect(conn.handleFrame(ccf, protocol.Encryption1RTT, protocol.ConnectionID{})).To(Succeed()) Expect(conn.handleFrame(ccf, protocol.Encryption1RTT, protocol.ConnectionID{})).To(Succeed())
Eventually(conn.Context().Done()).Should(BeClosed()) Eventually(conn.Context().Done()).Should(BeClosed())
Expect(context.Cause(conn.Context())).To(MatchError(testErr))
}) })
It("errors on HANDSHAKE_DONE frames", func() { It("errors on HANDSHAKE_DONE frames", func() {
@ -499,6 +500,7 @@ var _ = Describe("Connection", func() {
conn.CloseWithError(0x1337, "test error") conn.CloseWithError(0x1337, "test error")
Eventually(areConnsRunning).Should(BeFalse()) Eventually(areConnsRunning).Should(BeFalse())
Expect(conn.Context().Done()).To(BeClosed()) Expect(conn.Context().Done()).To(BeClosed())
Expect(context.Cause(conn.Context())).To(MatchError(expectedErr))
}) })
It("includes the frame type in transport-level close frames", func() { It("includes the frame type in transport-level close frames", func() {
@ -566,6 +568,7 @@ var _ = Describe("Connection", func() {
tracer.EXPECT().Close() tracer.EXPECT().Close()
conn.shutdown() conn.shutdown()
Eventually(returned).Should(BeClosed()) Eventually(returned).Should(BeClosed())
Expect(context.Cause(conn.Context())).To(MatchError(context.Canceled))
}) })
It("doesn't send any more packets after receiving a CONNECTION_CLOSE", func() { It("doesn't send any more packets after receiving a CONNECTION_CLOSE", func() {
@ -2010,19 +2013,21 @@ var _ = Describe("Connection", func() {
tracer.EXPECT().Close() tracer.EXPECT().Close()
conn.shutdown() conn.shutdown()
Eventually(done).Should(BeClosed()) Eventually(done).Should(BeClosed())
Expect(context.Cause(conn.Context())).To(MatchError(context.Canceled))
}) })
It("passes errors to the connection runner", func() { It("passes errors to the connection runner", func() {
testErr := errors.New("handshake error") testErr := errors.New("handshake error")
expectedErr := &qerr.ApplicationError{
ErrorCode: 0x1337,
ErrorMessage: testErr.Error(),
}
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
defer GinkgoRecover() defer GinkgoRecover()
cryptoSetup.EXPECT().StartHandshake().MaxTimes(1) cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
err := conn.run() err := conn.run()
Expect(err).To(MatchError(&qerr.ApplicationError{ Expect(err).To(MatchError(expectedErr))
ErrorCode: 0x1337,
ErrorMessage: testErr.Error(),
}))
close(done) close(done)
}() }()
streamManager.EXPECT().CloseWithError(gomock.Any()) streamManager.EXPECT().CloseWithError(gomock.Any())
@ -2034,6 +2039,7 @@ var _ = Describe("Connection", func() {
tracer.EXPECT().Close() tracer.EXPECT().Close()
Expect(conn.CloseWithError(0x1337, testErr.Error())).To(Succeed()) Expect(conn.CloseWithError(0x1337, testErr.Error())).To(Succeed())
Eventually(done).Should(BeClosed()) Eventually(done).Should(BeClosed())
Expect(context.Cause(conn.Context())).To(MatchError(expectedErr))
}) })
Context("transport parameters", func() { Context("transport parameters", func() {

View file

@ -178,6 +178,8 @@ type Connection interface {
// The error string will be sent to the peer. // The error string will be sent to the peer.
CloseWithError(ApplicationErrorCode, string) error CloseWithError(ApplicationErrorCode, string) error
// Context returns a context that is cancelled when the connection is closed. // Context returns a context that is cancelled when the connection is closed.
// The cancellation cause is set to the error that caused the connection to
// close, or `context.Canceled` in case the listener is closed first.
Context() context.Context Context() context.Context
// ConnectionState returns basic details about the QUIC connection. // ConnectionState returns basic details about the QUIC connection.
// Warning: This API should not be considered stable and might change soon. // Warning: This API should not be considered stable and might change soon.