Skip to content

Commit eac9cd0

Browse files
authored
server: make clientConn() thread-safe (pingcap#49073) (pingcap#49105)
ref pingcap#48224
1 parent 79bcffb commit eac9cd0

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

server/conn.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ type clientConn struct {
152152
lastActive time.Time // last active time
153153
authPlugin string // default authentication plugin
154154
isUnixSocket bool // connection is Unix Socket file
155+
closeOnce sync.Once // closeOnce is used to make sure clientConn closes only once
155156
rsEncoder *resultEncoder // rsEncoder is used to encode the string result to different charsets.
156157
inputDecoder *inputDecoder // inputDecoder is used to decode the different charsets of incoming strings to utf-8.
157158
socketCredUID uint32 // UID from the other end of the Unix Socket
@@ -320,21 +321,33 @@ func (cc *clientConn) Close() error {
320321
}
321322

322323
func closeConn(cc *clientConn, connections int) error {
323-
metrics.ConnGauge.Set(float64(connections))
324-
if cc.bufReadConn != nil {
325-
err := cc.bufReadConn.Close()
326-
if err != nil {
327-
// We need to expect connection might have already disconnected.
328-
// This is because closeConn() might be called after a connection read-timeout.
329-
logutil.Logger(context.Background()).Debug("could not close connection", zap.Error(err))
324+
var err error
325+
cc.closeOnce.Do(func() {
326+
metrics.ConnGauge.Set(float64(connections))
327+
328+
if cc.bufReadConn != nil {
329+
err = cc.bufReadConn.Close()
330+
if err != nil {
331+
// We need to expect connection might have already disconnected.
332+
// This is because closeConn() might be called after a connection read-timeout.
333+
logutil.Logger(context.Background()).Debug("could not close connection", zap.Error(err))
334+
}
335+
if cc.bufReadConn != nil {
336+
err = cc.bufReadConn.Close()
337+
if err != nil {
338+
// We need to expect connection might have already disconnected.
339+
// This is because closeConn() might be called after a connection read-timeout.
340+
logutil.Logger(context.Background()).Debug("could not close connection", zap.Error(err))
341+
}
342+
}
343+
// Close statements and session
344+
// This will release advisory locks, row locks, etc.
345+
if ctx := cc.getCtx(); ctx != nil {
346+
err = ctx.Close()
347+
}
330348
}
331-
}
332-
// Close statements and session
333-
// This will release advisory locks, row locks, etc.
334-
if ctx := cc.getCtx(); ctx != nil {
335-
return ctx.Close()
336-
}
337-
return nil
349+
})
350+
return err
338351
}
339352

340353
func (cc *clientConn) closeWithoutLock() error {

server/conn_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"io"
2626
"path/filepath"
2727
"strings"
28+
"sync"
2829
"sync/atomic"
2930
"testing"
3031
"time"
@@ -2025,3 +2026,44 @@ func TestLDAPAuthSwitch(t *testing.T) {
20252026
require.NoError(t, err)
20262027
require.Equal(t, []byte(mysql.AuthMySQLClearPassword), respAuthSwitch)
20272028
}
2029+
2030+
func TestCloseConn(t *testing.T) {
2031+
var outBuffer bytes.Buffer
2032+
2033+
store, _ := testkit.CreateMockStoreAndDomain(t)
2034+
cfg := newTestConfig()
2035+
cfg.Port = 0
2036+
cfg.Status.StatusPort = 0
2037+
drv := NewTiDBDriver(store)
2038+
server, err := NewServer(cfg, drv)
2039+
require.NoError(t, err)
2040+
2041+
cc := &clientConn{
2042+
connectionID: 0,
2043+
salt: []byte{
2044+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
2045+
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
2046+
},
2047+
server: server,
2048+
pkt: &packetIO{
2049+
bufWriter: bufio.NewWriter(&outBuffer),
2050+
},
2051+
collation: mysql.DefaultCollationID,
2052+
peerHost: "localhost",
2053+
alloc: arena.NewAllocator(512),
2054+
chunkAlloc: chunk.NewAllocator(),
2055+
capability: mysql.ClientProtocol41,
2056+
}
2057+
2058+
var wg sync.WaitGroup
2059+
const numGoroutines = 10
2060+
wg.Add(numGoroutines)
2061+
for i := 0; i < numGoroutines; i++ {
2062+
go func() {
2063+
defer wg.Done()
2064+
err := closeConn(cc, 1)
2065+
require.NoError(t, err)
2066+
}()
2067+
}
2068+
wg.Wait()
2069+
}

0 commit comments

Comments
 (0)