src/crypto/tls/conn.go | 2 +- src/crypto/tls/quic.go | 21 +++++++++------------ src/crypto/tls/quic_test.go | 19 +++++++++++++++++++ diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index a840125a45eae71a2c1214eb7b9e8a2f1daa21d9..9c662ef8f67c0ee7ec264d7917c8bdbaa6fe8570 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -1531,7 +1531,7 @@ // this cancellation. In the former case, we need to close the connection. defer cancel() if c.quic != nil { - c.quic.cancelc = handshakeCtx.Done() + c.quic.ctx = handshakeCtx c.quic.cancel = cancel } else if ctx.Done() != nil { // Close the connection if ctx is canceled before the function returns. diff --git a/src/crypto/tls/quic.go b/src/crypto/tls/quic.go index 76b7eb2cbd8ee8d697cf370335f57b88466763d9..c7d8508acb7be6a79c6f72a5946b10f83d85958c 100644 --- a/src/crypto/tls/quic.go +++ b/src/crypto/tls/quic.go @@ -162,7 +162,7 @@ started bool signalc chan struct{} // handshake data is available to be read blockedc chan struct{} // handshake is waiting for data, closed when done - cancelc <-chan struct{} // handshake has been canceled + ctx context.Context // handshake context cancel context.CancelFunc waitingForDrain bool @@ -261,10 +261,11 @@ } // Close closes the connection and stops any in-progress handshake. func (q *QUICConn) Close() error { - if q.conn.quic.cancel == nil { + if q.conn.quic.ctx == nil { return nil // never started } q.conn.quic.cancel() + <-q.conn.quic.signalc for range q.conn.quic.blockedc { // Wait for the handshake goroutine to return. } @@ -511,20 +512,16 @@ defer c.handshakeMutex.Lock() // Send on blockedc to notify the QUICConn that the handshake is blocked. // Exported methods of QUICConn wait for the handshake to become blocked // before returning to the user. - select { - case c.quic.blockedc <- struct{}{}: - case <-c.quic.cancelc: - return c.sendAlertLocked(alertCloseNotify) - } + c.quic.blockedc <- struct{}{} // The QUICConn reads from signalc to notify us that the handshake may // be able to proceed. (The QUICConn reads, because we close signalc to // indicate that the handshake has completed.) - select { - case c.quic.signalc <- struct{}{}: - c.hand.Write(c.quic.readbuf) - c.quic.readbuf = nil - case <-c.quic.cancelc: + c.quic.signalc <- struct{}{} + if c.quic.ctx.Err() != nil { + // The connection has been canceled. return c.sendAlertLocked(alertCloseNotify) } + c.hand.Write(c.quic.readbuf) + c.quic.readbuf = nil return nil } diff --git a/src/crypto/tls/quic_test.go b/src/crypto/tls/quic_test.go index bd0eaa4d47efc6b56eff614693ac4430f0e89678..0cf8b337e161c96545dd3307a9c1fb2470735bbb 100644 --- a/src/crypto/tls/quic_test.go +++ b/src/crypto/tls/quic_test.go @@ -11,6 +11,7 @@ "errors" "fmt" "reflect" "strings" + "sync" "testing" ) @@ -478,6 +479,24 @@ } if calls != 1 { t.Errorf("GetConfigForClient called %v times, want 1", calls) } +} + +func TestQUICContextCancelation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + config := &QUICConfig{TLSConfig: testConfig.Clone()} + config.TLSConfig.MinVersion = VersionTLS13 + cli := newTestQUICClient(t, config) + cli.conn.SetTransportParameters(nil) + srv := newTestQUICServer(t, config) + srv.conn.SetTransportParameters(nil) + // Verify that canceling the connection context concurrently does not cause any races. + // See https://go.dev/issue/77274. + var wg sync.WaitGroup + wg.Go(func() { + _ = runTestQUICConnection(ctx, cli, srv, nil) + }) + wg.Go(cancel) + wg.Wait() } func TestQUICDelayedTransportParameters(t *testing.T) {