Skip to content

Commit 7b29b6b

Browse files
committed
bugfix: prevent segfaults in connection_pool
After the patch ConnectionPool.getNextConnection() does not return (nil, nil). It always return a connection or an error. The check Connection.ConnectedNow() does not have sence because the connection may be closed right after the GetNextConnection() call. The code just complicates the logic and does not protect against anything. A chain of two atomic operations IsEmpty() + GetNextConnection() wrong because it leads too a race condition.
1 parent f93347a commit 7b29b6b

File tree

3 files changed

+50
-52
lines changed

3 files changed

+50
-52
lines changed

connection_pool/connection_pool.go

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,20 @@ func (connPool *ConnectionPool) ConnectedNow(mode Mode) (bool, error) {
130130
if connPool.getState() != connConnected {
131131
return false, nil
132132
}
133-
134-
conn, err := connPool.getNextConnection(mode)
135-
if err != nil || conn == nil {
136-
return false, err
133+
switch mode {
134+
case ANY:
135+
return !connPool.anyPool.IsEmpty(), nil
136+
case RW:
137+
return !connPool.rwPool.IsEmpty(), nil
138+
case RO:
139+
return !connPool.roPool.IsEmpty(), nil
140+
case PreferRW:
141+
fallthrough
142+
case PreferRO:
143+
return !connPool.rwPool.IsEmpty() || !connPool.roPool.IsEmpty(), nil
144+
default:
145+
return false, ErrNoHealthyInstance
137146
}
138-
139-
return conn.ConnectedNow(), nil
140147
}
141148

142149
// ConfiguredTimeout gets timeout of current connection.
@@ -751,49 +758,34 @@ func (connPool *ConnectionPool) getNextConnection(mode Mode) (*tarantool.Connect
751758

752759
switch mode {
753760
case ANY:
754-
if connPool.anyPool.IsEmpty() {
755-
return nil, ErrNoHealthyInstance
761+
if next := connPool.anyPool.GetNextConnection(); next != nil {
762+
return next, nil
756763
}
757-
758-
return connPool.anyPool.GetNextConnection(), nil
759-
760764
case RW:
761-
if connPool.rwPool.IsEmpty() {
762-
return nil, ErrNoRwInstance
765+
if next := connPool.rwPool.GetNextConnection(); next != nil {
766+
return next, nil
763767
}
764-
765-
return connPool.rwPool.GetNextConnection(), nil
766-
768+
return nil, ErrNoRwInstance
767769
case RO:
768-
if connPool.roPool.IsEmpty() {
769-
return nil, ErrNoRoInstance
770+
if next := connPool.roPool.GetNextConnection(); next != nil {
771+
return next, nil
770772
}
771-
772-
return connPool.roPool.GetNextConnection(), nil
773-
773+
return nil, ErrNoRoInstance
774774
case PreferRW:
775-
if !connPool.rwPool.IsEmpty() {
776-
return connPool.rwPool.GetNextConnection(), nil
775+
if next := connPool.rwPool.GetNextConnection(); next != nil {
776+
return next, nil
777777
}
778-
779-
if !connPool.roPool.IsEmpty() {
780-
return connPool.roPool.GetNextConnection(), nil
778+
if next := connPool.roPool.GetNextConnection(); next != nil {
779+
return next, nil
781780
}
782-
783-
return nil, ErrNoHealthyInstance
784-
785781
case PreferRO:
786-
if !connPool.roPool.IsEmpty() {
787-
return connPool.roPool.GetNextConnection(), nil
782+
if next := connPool.roPool.GetNextConnection(); next != nil {
783+
return next, nil
788784
}
789-
790-
if !connPool.rwPool.IsEmpty() {
791-
return connPool.rwPool.GetNextConnection(), nil
785+
if next := connPool.rwPool.GetNextConnection(); next != nil {
786+
return next, nil
792787
}
793-
794-
return nil, ErrNoHealthyInstance
795788
}
796-
797789
return nil, ErrNoHealthyInstance
798790
}
799791

connection_pool/round_robin.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package connection_pool
22

33
import (
4+
"math"
45
"sync"
56
"sync/atomic"
67

@@ -66,29 +67,18 @@ func (r *RoundRobinStrategy) GetNextConnection() *tarantool.Connection {
6667
r.mutex.RLock()
6768
defer r.mutex.RUnlock()
6869

69-
// We want to iterate through the elements in a circular order
70-
// so the first element in cycle is connections[next]
71-
// and the last one is connections[next + length].
72-
next := r.nextIndex()
73-
cycleLen := len(r.conns) + next
74-
for i := next; i < cycleLen; i++ {
75-
idx := i % len(r.conns)
76-
if r.conns[idx].ConnectedNow() {
77-
if i != next {
78-
atomic.StoreUint64(&r.current, uint64(idx))
79-
}
80-
return r.conns[idx]
81-
}
70+
if r.size == 0 {
71+
return nil
8272
}
83-
84-
return nil
73+
return r.conns[r.nextIndex()]
8574
}
8675

8776
func NewEmptyRoundRobin(size int) *RoundRobinStrategy {
8877
return &RoundRobinStrategy{
8978
conns: make([]*tarantool.Connection, 0, size),
9079
indexByAddr: make(map[string]int),
9180
size: 0,
81+
current: math.MaxUint64, // next value is 0
9282
}
9383
}
9484

connection_pool/round_robin_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,20 @@ func TestRoundRobinAddDuplicateDelete(t *testing.T) {
5252
}
5353
}
5454

55+
func TestRoundRobinGetNextConnection(t *testing.T) {
56+
rr := NewEmptyRoundRobin(10)
57+
58+
addrs := []string{validAddr1, validAddr2}
59+
conns := []*tarantool.Connection{&tarantool.Connection{}, &tarantool.Connection{}}
5560

61+
for i, addr := range addrs {
62+
rr.AddConn(addr, conns[i])
63+
}
64+
65+
expectedConns := []*tarantool.Connection{conns[0], conns[1], conns[0], conns[1]}
66+
for i, expected := range expectedConns {
67+
if rr.GetNextConnection() != expected {
68+
t.Errorf("Unexpected connection on %d call", i)
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)