x/net/http2: New wrapped http2 implementation (go1.27+) missing single flight mechanism
Author: FanglidingCreated Sep 22, 2026Updated Sep 28, 2026
LabelsNeedsFix
Go version
go 1.27 linux/amd64
Output of go env in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/codespace/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/codespace/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2735046389=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/workspaces/Xray-core/go.mod'
GOMODCACHE='/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/codespace/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.26.1'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
run this code to count dial calls in http2
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"sync"
"sync/atomic"
"golang.org/x/net/http2"
)
func main() {
totalRequests := 100
targetURL := "https://cp.cloudflare.com/cdn-cgi/trace"
var dialCount atomic.Int64
client := &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (c net.Conn, err error) {
dialCount.Add(1)
dialer := &tls.Dialer{Config: cfg}
return dialer.DialContext(ctx, network, addr)
},
},
}
var wg sync.WaitGroup
start := make(chan struct{})
for range totalRequests {
wg.Add(1)
go func() {
defer wg.Done()
<-start
resp, err := client.Get(targetURL)
if err == nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
}()
}
close(start)
wg.Wait()
fmt.Printf("Total Dial Calls: %d\n", dialCount.Load())
}What did you see happen?
It made 100 tcp dial calls for every http request
Total Dial Calls: 100What did you expect to see?
In old http2 implementation, instantly sending multiple requests will only make one TCP dial, this is the expected behavior of http2 run code above with this
go run -tags http2legacy .And it will return
Total Dial Calls: 1I understand that HTTP package needs to dial multiple connections because the server HTTP version is unknown, but we have prior knowledge in net/x/http2, and perhaps we can add a config to enable the single flight mechanism in http package (or automatically use when alpn only contains h2?)
Source: golang/go