Open georgehao opened 7 months ago
actually, this data race
can avoid by pass ctx.Copy()
. and this case is very hard to trigger, but for thread safe, it's better to consider it.
Just a question, in what scenarios is it necessary to pass the Gin context into Gorm's? After all, Gorm is primarily used for controlling database-related operations, such as querying with timeouts. The context in Gin seems fundamentally different from the context in Gorm
but the profiles I know are more SRE oriented, not really on protocol side.
yes, but sometimes developer will directly passes the gin.Context to db interface, it will becomes a issue. (I always pass like this)
Wow, Good findings @georgehao
If I understand correctly, the race is happen between go rs.awaitDone(ctx, txctx)
from sql package and the gin ServeHTTP
that trying to serve new request comes in.
So, there are 2 goroutines run parallel.
The first one is from go rs.awaitDone(ctx, txctx)
which trying to read *http.Request to get the context.Context from std http lib.
The second goroutine is from a new request comes in and got a same reference of gin.Context (previous request already marked as finished by gin and gin put the gin.Context back to the pool) trying to write the new http.Request to the gin.Context
The second goroutine is from a new request comes in and got a same reference of gin.Context (previous request already marked as finished by gin and gin put the gin.Context back to the pool) trying to write the new http.Request to the gin.Context
Almost correct, only tweak a little. owing to rs.close(ctx.Err())
=> ctx.Err()
cause the data race
.
Almost correct, only tweak a little. owing to
rs.close(ctx.Err())
=>ctx.Err()
cause thedata race
.
If I read your data race stack trace, the race comes from <-ctx.Done()
which trying to call gin HasRequestContext reading the http.Request, but at the same time the new request trying to store new http.Request in same *gin.Context
Oh my bad I didn't notice that the stacktrace shows the race comes from ctx.Err()
that trying to read *gin.Context.Request
Just pass c.Request.Context()
instead of c
(*gin.Context) itself.
db.WithContext(c.Request.Context())
Also consider not passing request's context to a DBMS transaction when performing INSERT/UPDATE/DELETE. Otherwise your transaction may rollback if request is canceled by client (by accidental page refresh, for example, if client is a web application). Create separate context with timeout instead.
Description
How to reproduce
go run -race main.go
you will see the
DATA RACE
The reason
When
gin
server starting, will callServeHTTP
. This a goroutine, allocates from go lib http https://github.com/gin-gonic/gin/blob/master/gin.go#L583C1-L592C2can see that
gin.Context
allocate fromsync.Pool
When call db
gorm will call
initContextClose
finally. https://github.com/golang/go/blob/go1.20.14/src/database/sql/sql.go#L2916-L2941can see here
initContextClose
will start a goroutine to dealctx
, this ctx isgin.Context
.think one case: if
go rs.awaitDone(ctx, txctx)
run over afterServeHTTP
goroutine. Thegin.Context
allocated fromsync.Pool
will be recovered and allocates for next request.This really a data race
DATA RACE
happens when go version large thango1.20
?I think go upgrade the race detector, the new race detector detects this.
Environment