Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions internal/http3/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,14 @@ func (t *Transport) AddConn(ctx context.Context, addr string) error {
}

func (t *Transport) getClient(ctx context.Context, hostname string, onlyCached bool) (rtc *roundTripperWithCount, isReused bool, err error) {
// AddConn reaches here without going through roundTripOpt, so init has to
// run here as well. Outside the mutex, like roundTripOpt does it, because
// init opens a UDP socket.
t.initOnce.Do(func() { t.initErr = t.init() })
if t.initErr != nil {
return nil, false, t.initErr
}

t.mutex.Lock()
defer t.mutex.Unlock()
if t.closed {
Expand Down
37 changes: 37 additions & 0 deletions internal/http3/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package http3
import (
"context"
"crypto/tls"
"errors"
"net/http"
"testing"
"time"
Expand Down Expand Up @@ -139,3 +140,39 @@ func TestNewClientConnAndRawClientConn(t *testing.T) {

t.Log("Successfully created ClientConn and RawClientConn")
}

// AddConn is the second way into getClient, beside roundTripOpt, and only
// roundTripOpt ran init. A transport whose first use is AddConn therefore
// dialed with a nil QUICConfig, a nil quic.Transport and a nil newClientConn,
// and the dial runs in its own goroutine, so the nil transport took the
// process down with it.
func TestAddConnInitializesTheTransport(t *testing.T) {
dialed := make(chan *quic.Config, 1)
tr := &Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
dialed <- cfg
return nil, errors.New("this test does not dial")
},
}

if err := tr.AddConn(context.Background(), "127.0.0.1:443"); err != nil {
t.Fatalf("AddConn: %v", err)
}

var cfg *quic.Config
select {
case cfg = <-dialed:
case <-time.After(10 * time.Second):
t.Fatal("the dial never ran")
}

if cfg == nil {
t.Fatal("dialed with a nil quic.Config, so init did not run before AddConn dialed")
}
if cfg.MaxIncomingStreams != -1 {
t.Errorf("MaxIncomingStreams is %d, want -1", cfg.MaxIncomingStreams)
}
if len(cfg.Versions) != 1 {
t.Errorf("Versions is %v, want exactly one", cfg.Versions)
}
}