forked from mirror/redis
Merge pull request #173 from go-redis/fix/pool-panic-on-retry
Fix pool panic on slow connection with MaxRetries > 0.
This commit is contained in:
commit
0880b0b20c
7
conn.go
7
conn.go
|
@ -43,11 +43,8 @@ func (cn *conn) init(opt *Options) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use connection to connect to Redis.
|
// Temp client for Auth and Select.
|
||||||
pool := newSingleConnPoolConn(cn)
|
client := newClient(opt, newSingleConnPool(cn))
|
||||||
|
|
||||||
// Client is not closed because we want to reuse underlying connection.
|
|
||||||
client := newClient(opt, pool)
|
|
||||||
|
|
||||||
if opt.Password != "" {
|
if opt.Password != "" {
|
||||||
if err := client.Auth(opt.Password).Err(); err != nil {
|
if err := client.Auth(opt.Password).Err(); err != nil {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package redis_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"gopkg.in/redis.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("newConnDialer with bad connection", func() {
|
||||||
|
It("should return an error", func() {
|
||||||
|
dialer := redis.NewConnDialer(&redis.Options{
|
||||||
|
Dialer: func() (net.Conn, error) {
|
||||||
|
return &badConn{}, nil
|
||||||
|
},
|
||||||
|
MaxRetries: 3,
|
||||||
|
Password: "password",
|
||||||
|
DB: 1,
|
||||||
|
})
|
||||||
|
_, err := dialer()
|
||||||
|
Expect(err).To(MatchError("bad connection"))
|
||||||
|
})
|
||||||
|
})
|
|
@ -6,6 +6,8 @@ func (c *baseClient) Pool() pool {
|
||||||
return c.connPool
|
return c.connPool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var NewConnDialer = newConnDialer
|
||||||
|
|
||||||
func (cn *conn) SetNetConn(netcn net.Conn) {
|
func (cn *conn) SetNetConn(netcn net.Conn) {
|
||||||
cn.netcn = netcn
|
cn.netcn = netcn
|
||||||
}
|
}
|
||||||
|
|
14
main_test.go
14
main_test.go
|
@ -232,7 +232,15 @@ func startSentinel(port, masterName, masterPort string) (*redisProcess, error) {
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
var errTimeout = syscall.ETIMEDOUT
|
var (
|
||||||
|
errTimeout = syscall.ETIMEDOUT
|
||||||
|
)
|
||||||
|
|
||||||
|
type badConnError string
|
||||||
|
|
||||||
|
func (e badConnError) Error() string { return string(e) }
|
||||||
|
func (e badConnError) Timeout() bool { return false }
|
||||||
|
func (e badConnError) Temporary() bool { return false }
|
||||||
|
|
||||||
type badConn struct {
|
type badConn struct {
|
||||||
net.TCPConn
|
net.TCPConn
|
||||||
|
@ -250,7 +258,7 @@ func (cn *badConn) Read([]byte) (int, error) {
|
||||||
if cn.readErr != nil {
|
if cn.readErr != nil {
|
||||||
return 0, cn.readErr
|
return 0, cn.readErr
|
||||||
}
|
}
|
||||||
return 0, net.UnknownNetworkError("badConn")
|
return 0, badConnError("bad connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *badConn) Write([]byte) (int, error) {
|
func (cn *badConn) Write([]byte) (int, error) {
|
||||||
|
@ -260,5 +268,5 @@ func (cn *badConn) Write([]byte) (int, error) {
|
||||||
if cn.writeErr != nil {
|
if cn.writeErr != nil {
|
||||||
return 0, cn.writeErr
|
return 0, cn.writeErr
|
||||||
}
|
}
|
||||||
return 0, net.UnknownNetworkError("badConn")
|
return 0, badConnError("bad connection")
|
||||||
}
|
}
|
||||||
|
|
2
multi.go
2
multi.go
|
@ -22,7 +22,7 @@ func (c *Client) Multi() *Multi {
|
||||||
multi := &Multi{
|
multi := &Multi{
|
||||||
base: &baseClient{
|
base: &baseClient{
|
||||||
opt: c.opt,
|
opt: c.opt,
|
||||||
connPool: newSingleConnPool(c.connPool, true),
|
connPool: newStickyConnPool(c.connPool, true),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
multi.commandable.process = multi.process
|
multi.commandable.process = multi.process
|
||||||
|
|
92
pool.go
92
pool.go
|
@ -314,6 +314,52 @@ func (p *connPool) reaper() {
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type singleConnPool struct {
|
type singleConnPool struct {
|
||||||
|
cn *conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSingleConnPool(cn *conn) *singleConnPool {
|
||||||
|
return &singleConnPool{
|
||||||
|
cn: cn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) First() *conn {
|
||||||
|
return p.cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) Get() (*conn, error) {
|
||||||
|
return p.cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) Put(cn *conn) error {
|
||||||
|
if p.cn != cn {
|
||||||
|
panic("p.cn != cn")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) Remove(cn *conn) error {
|
||||||
|
if p.cn != cn {
|
||||||
|
panic("p.cn != cn")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) FreeLen() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleConnPool) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type stickyConnPool struct {
|
||||||
pool pool
|
pool pool
|
||||||
reusable bool
|
reusable bool
|
||||||
|
|
||||||
|
@ -322,27 +368,21 @@ type singleConnPool struct {
|
||||||
mx sync.Mutex
|
mx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSingleConnPool(pool pool, reusable bool) *singleConnPool {
|
func newStickyConnPool(pool pool, reusable bool) *stickyConnPool {
|
||||||
return &singleConnPool{
|
return &stickyConnPool{
|
||||||
pool: pool,
|
pool: pool,
|
||||||
reusable: reusable,
|
reusable: reusable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSingleConnPoolConn(cn *conn) *singleConnPool {
|
func (p *stickyConnPool) First() *conn {
|
||||||
return &singleConnPool{
|
|
||||||
cn: cn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *singleConnPool) First() *conn {
|
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
cn := p.cn
|
cn := p.cn
|
||||||
p.mx.Unlock()
|
p.mx.Unlock()
|
||||||
return cn
|
return cn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) Get() (*conn, error) {
|
func (p *stickyConnPool) Get() (*conn, error) {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
|
|
||||||
|
@ -362,15 +402,13 @@ func (p *singleConnPool) Get() (*conn, error) {
|
||||||
return p.cn, nil
|
return p.cn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) put() (err error) {
|
func (p *stickyConnPool) put() (err error) {
|
||||||
if p.pool != nil {
|
err = p.pool.Put(p.cn)
|
||||||
err = p.pool.Put(p.cn)
|
|
||||||
}
|
|
||||||
p.cn = nil
|
p.cn = nil
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) Put(cn *conn) error {
|
func (p *stickyConnPool) Put(cn *conn) error {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
if p.cn != cn {
|
if p.cn != cn {
|
||||||
|
@ -382,30 +420,32 @@ func (p *singleConnPool) Put(cn *conn) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) remove() (err error) {
|
func (p *stickyConnPool) remove() (err error) {
|
||||||
if p.pool != nil {
|
err = p.pool.Remove(p.cn)
|
||||||
err = p.pool.Remove(p.cn)
|
|
||||||
}
|
|
||||||
p.cn = nil
|
p.cn = nil
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) Remove(cn *conn) error {
|
func (p *stickyConnPool) Remove(cn *conn) error {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
if p.cn == nil {
|
if p.cn == nil {
|
||||||
panic("p.cn == nil")
|
panic("p.cn == nil")
|
||||||
}
|
}
|
||||||
if cn != nil && cn != p.cn {
|
if cn != nil && p.cn != cn {
|
||||||
panic("cn != p.cn")
|
panic("p.cn != cn")
|
||||||
}
|
}
|
||||||
if p.closed {
|
if p.closed {
|
||||||
return errClosed
|
return errClosed
|
||||||
}
|
}
|
||||||
return p.remove()
|
if cn == nil {
|
||||||
|
return p.remove()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) Len() int {
|
func (p *stickyConnPool) Len() int {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
if p.cn == nil {
|
if p.cn == nil {
|
||||||
|
@ -414,7 +454,7 @@ func (p *singleConnPool) Len() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) FreeLen() int {
|
func (p *stickyConnPool) FreeLen() int {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
if p.cn == nil {
|
if p.cn == nil {
|
||||||
|
@ -423,7 +463,7 @@ func (p *singleConnPool) FreeLen() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleConnPool) Close() error {
|
func (p *stickyConnPool) Close() error {
|
||||||
defer p.mx.Unlock()
|
defer p.mx.Unlock()
|
||||||
p.mx.Lock()
|
p.mx.Lock()
|
||||||
if p.closed {
|
if p.closed {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"gopkg.in/redis.v3"
|
"gopkg.in/redis.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Pool", func() {
|
var _ = Describe("pool", func() {
|
||||||
var client *redis.Client
|
var client *redis.Client
|
||||||
|
|
||||||
var perform = func(n int, cb func()) {
|
var perform = func(n int, cb func()) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ func (c *Client) PubSub() *PubSub {
|
||||||
return &PubSub{
|
return &PubSub{
|
||||||
baseClient: &baseClient{
|
baseClient: &baseClient{
|
||||||
opt: c.opt,
|
opt: c.opt,
|
||||||
connPool: newSingleConnPool(c.connPool, false),
|
connPool: newStickyConnPool(c.connPool, false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,8 @@ var _ = Describe("Client", func() {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
cn.SetNetConn(&badConn{})
|
cn.SetNetConn(&badConn{})
|
||||||
Expect(client.Pool().Put(cn)).NotTo(HaveOccurred())
|
err = client.Pool().Put(cn)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
err = client.Ping().Err()
|
err = client.Ping().Err()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (c *sentinelClient) PubSub() *PubSub {
|
||||||
return &PubSub{
|
return &PubSub{
|
||||||
baseClient: &baseClient{
|
baseClient: &baseClient{
|
||||||
opt: c.opt,
|
opt: c.opt,
|
||||||
connPool: newSingleConnPool(c.connPool, false),
|
connPool: newStickyConnPool(c.connPool, false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue