Skip to content

Commit ce66b1d

Browse files
committed
Fix data race with TraceLog.Config initialization
#2120
1 parent d1205a6 commit ce66b1d

File tree

4 files changed

+69
-9
lines changed

4 files changed

+69
-9
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ require (
88
github.com/jackc/puddle/v2 v2.2.1
99
github.com/stretchr/testify v1.8.1
1010
golang.org/x/crypto v0.17.0
11+
golang.org/x/sync v0.8.0
1112
golang.org/x/text v0.14.0
1213
)
1314

1415
require (
1516
github.com/davecgh/go-spew v1.1.1 // indirect
1617
github.com/kr/pretty v0.3.0 // indirect
1718
github.com/pmezard/go-difflib v1.0.0 // indirect
18-
golang.org/x/sync v0.1.0 // indirect
1919
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2020
gopkg.in/yaml.v3 v3.0.1 // indirect
2121
)

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
3131
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
3232
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
3333
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
34-
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
35-
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
34+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
35+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
3636
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
3737
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
3838
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

tracelog/tracelog.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"errors"
88
"fmt"
9+
"sync"
910
"time"
1011
"unicode/utf8"
1112

@@ -129,19 +130,25 @@ func DefaultTraceLogConfig() *TraceLogConfig {
129130
}
130131
}
131132

132-
// TraceLog implements pgx.QueryTracer, pgx.BatchTracer, pgx.ConnectTracer, and pgx.CopyFromTracer. All fields are
133-
// required.
133+
// TraceLog implements pgx.QueryTracer, pgx.BatchTracer, pgx.ConnectTracer, and pgx.CopyFromTracer. Logger and LogLevel
134+
// are required. Config will be automatically initialized on first use if nil.
134135
type TraceLog struct {
135136
Logger Logger
136137
LogLevel LogLevel
137-
Config *TraceLogConfig
138+
139+
Config *TraceLogConfig
140+
ensureConfigOnce sync.Once
138141
}
139142

140143
// ensureConfig initializes the Config field with default values if it is nil.
141144
func (tl *TraceLog) ensureConfig() {
142-
if tl.Config == nil {
143-
tl.Config = DefaultTraceLogConfig()
144-
}
145+
tl.ensureConfigOnce.Do(
146+
func() {
147+
if tl.Config == nil {
148+
tl.Config = DefaultTraceLogConfig()
149+
}
150+
},
151+
)
145152
}
146153

147154
type ctxKey int

tracelog/tracelog_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import (
66
"log"
77
"os"
88
"strings"
9+
"sync"
910
"testing"
1011
"time"
1112

1213
"github.com/jackc/pgx/v5"
14+
"github.com/jackc/pgx/v5/pgxpool"
1315
"github.com/jackc/pgx/v5/pgxtest"
1416
"github.com/jackc/pgx/v5/tracelog"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/require"
19+
"golang.org/x/sync/errgroup"
1720
)
1821

1922
var defaultConnTestRunner pgxtest.ConnTestRunner
@@ -35,18 +38,29 @@ type testLog struct {
3538

3639
type testLogger struct {
3740
logs []testLog
41+
42+
mux sync.Mutex
3843
}
3944

4045
func (l *testLogger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) {
46+
l.mux.Lock()
47+
defer l.mux.Unlock()
48+
4149
data["ctxdata"] = ctx.Value("ctxdata")
4250
l.logs = append(l.logs, testLog{lvl: level, msg: msg, data: data})
4351
}
4452

4553
func (l *testLogger) Clear() {
54+
l.mux.Lock()
55+
defer l.mux.Unlock()
56+
4657
l.logs = l.logs[0:0]
4758
}
4859

4960
func (l *testLogger) FilterByMsg(msg string) (res []testLog) {
61+
l.mux.Lock()
62+
defer l.mux.Unlock()
63+
5064
for _, log := range l.logs {
5165
if log.msg == msg {
5266
res = append(res, log)
@@ -457,3 +471,42 @@ func TestLogPrepare(t *testing.T) {
457471
require.Equal(t, err, logger.logs[0].data["err"])
458472
})
459473
}
474+
475+
// https://github.com/jackc/pgx/pull/2120
476+
func TestConcurrentUsage(t *testing.T) {
477+
t.Parallel()
478+
479+
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
480+
defer cancel()
481+
482+
logger := &testLogger{}
483+
tracer := &tracelog.TraceLog{
484+
Logger: logger,
485+
LogLevel: tracelog.LogLevelTrace,
486+
}
487+
488+
config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
489+
require.NoError(t, err)
490+
config.ConnConfig.Tracer = tracer
491+
492+
for i := 0; i < 50; i++ {
493+
func() {
494+
pool, err := pgxpool.NewWithConfig(ctx, config)
495+
require.NoError(t, err)
496+
497+
defer pool.Close()
498+
499+
eg := errgroup.Group{}
500+
501+
for i := 0; i < 5; i++ {
502+
eg.Go(func() error {
503+
_, err := pool.Exec(ctx, `select 1`)
504+
return err
505+
})
506+
}
507+
508+
err = eg.Wait()
509+
require.NoError(t, err)
510+
}()
511+
}
512+
}

0 commit comments

Comments
 (0)