Description
Go version
go version go1.23.5 darwin/arm64
Output of go env
in your module/workspace:
GO111MODULE='on'
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/yzy/Library/Caches/go-build'
GOENV='/Users/yzy/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/yzy/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/yzy/go'
GOPRIVATE=''
GOPROXY='https://goproxy.cn,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.23.5'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/yzy/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/yzy/GolandProjects/ai_services/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/2f/v169q89x0h9cg8lknlk8s3xr0000gn/T/go-build1486772356=/tmp/go-build -gno-record-gcc-switches -fno-common'
What did you do?
The service uses the GIN framework, and when concurrent GET requests for static resources are made from the same IP address, the following occurs:
fatal error: concurrent map iteration and map write
goroutine 102500 [running]:
net/http.http2cloneHeader(...)
/usr/local/go/src/net/http/h2_bundle.go:6817
net/http.(*http2responseWriterState).writeHeader(0xc0000f2e00, 0xc000013e11?)
/usr/local/go/src/net/http/h2_bundle.go:6811 +0xd8
net/http.(*http2responseWriter).WriteHeader(0x8ea88278fa88a?, 0x10425e0?)
/usr/local/go/src/net/http/h2_bundle.go:6775 +0x1b
github.com/gin-gonic/gin.(*responseWriter).WriteHeaderNow(...)
/Users/yzy/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/response_writer.go:77
github.com/gin-gonic/gin.(*responseWriter).Write(0xc0001c0100, {0xc00023e612, 0xa, 0xa})
/Users/yzy/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/response_writer.go:82 +0x4d
compress/gzip.(*Writer).Write(0xc00023e5a0, {0xfe18f0, 0x12, 0x12})
/usr/local/go/src/compress/gzip/gzip.go:168 +0x13b
github.com/gin-contrib/gzip.(*gzipWriter).Write(0xc0005c05e8, {0xfe18f0, 0x12, 0x12})
/Users/yzy/go/pkg/mod/github.com/gin-contrib/gzip@v1.2.3/gzip.go:37 +0x85
github.com/gin-gonic/gin.serveError(0xc00009f200, 0x194, {0xfe18f0, 0x12, 0x12})
/Users/yzy/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:683 +0x124
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc000025520, 0xc00009f200)
/Users/yzy/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:670 +0x4c5
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc000025520, {0xbedd08, 0xc0004e55e8}, 0xc000299040)
/Users/yzy/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589 +0x1b2
net/http.serverHandler.ServeHTTP({0x2c?}, {0xbedd08?, 0xc0004e55e8?}, 0xc0002f9000?)
/usr/local/go/src/net/http/server.go:3210 +0x8e
net/http.initALPNRequest.ServeHTTP({{0xbeeb70?, 0xc00048a060?}, 0xc000004a88?, {0xc000156000?}}, {0xbedd08, 0xc0004e55e8}, 0xc000299040)
/usr/local/go/src/net/http/server.go:3819 +0x231
net/http.(*http2serverConn).runHandler(0x44323b?, 0x0?, 0x0?, 0x0?)
/usr/local/go/src/net/http/h2_bundle.go:6249 +0xf5
created by net/http.(*http2serverConn).scheduleHandler in goroutine 102371
/usr/local/go/src/net/http/h2_bundle.go:6183 +0x21d
goroutine 1 [IO wait, 1 minutes]:
internal/poll.runtime_pollWait(0x7f0bb66da680, 0x72)
/usr/local/go/src/runtime/netpoll.go:351 +0x85
internal/poll.(*pollDesc).wait(0xc0000f2680?, 0x900000036?, 0x0)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x27
internal/poll.(*pollDesc).waitRead(...)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc0000f2680)
/usr/local/go/src/internal/poll/fd_unix.go:620 +0x295
net.(*netFD).accept(0xc0000f2680)
/usr/local/go/src/net/fd_unix.go:172 +0x29
net.(*TCPListener).accept(0xc000093900)
/usr/local/go/src/net/tcpsock_posix.go:159 +0x1e
net.(*TCPListener).Accept(0xc000093900)
/usr/local/go/src/net/tcpsock.go:372 +0x30
What did you see happen?
It seems that the http2cloneHeader method is iterating over parameters without acquiring a read lock, leading to conflicts in a multi-concurrent environment.
if len(rws.handlerHeader) > 0 {
rws.snapHeader = http2cloneHeader(rws.handlerHeader)
}
func http2cloneHeader(h Header) Header {
h2 := make(Header, len(h))
for k, vv := range h {
vv2 := make([]string, len(vv))
copy(vv2, vv)
h2[k] = vv2
}
return h2
}
What did you expect to see?
Can we improve the occurrence of this small probability problem through read-write locks?