redis/pool.go

541 lines
9.8 KiB
Go
Raw Normal View History

2013-07-02 15:17:31 +04:00
package redis
import (
2013-11-07 18:20:15 +04:00
"errors"
2015-01-17 12:56:34 +03:00
"fmt"
2013-07-02 15:17:31 +04:00
"sync"
2015-01-31 16:20:37 +03:00
"sync/atomic"
2013-07-02 15:17:31 +04:00
"time"
"gopkg.in/bsm/ratelimit.v1"
2013-07-02 15:17:31 +04:00
)
2013-11-07 18:20:15 +04:00
var (
2015-01-31 16:20:37 +03:00
errClosed = errors.New("redis: client is closed")
errPoolTimeout = errors.New("redis: connection pool timeout")
2013-11-07 18:20:15 +04:00
)
2016-01-25 16:57:09 +03:00
// PoolStats contains pool state information and accumulated stats.
2016-01-19 19:36:40 +03:00
type PoolStats struct {
Requests uint32 // number of times a connection was requested by the pool
Waits uint32 // number of times our pool had to wait for a connection
Timeouts uint32 // number of times a wait timeout occurred
2016-01-19 19:36:40 +03:00
TotalConns uint32 // the number of total connections in the pool
FreeConns uint32 // the number of free connections in the pool
2016-01-19 19:36:40 +03:00
}
2013-07-02 15:17:31 +04:00
type pool interface {
First() *conn
Get() (*conn, bool, error)
2013-07-02 15:17:31 +04:00
Put(*conn) error
2015-12-22 16:45:03 +03:00
Remove(*conn, error) error
2013-07-02 15:17:31 +04:00
Len() int
FreeLen() int
2013-07-02 15:17:31 +04:00
Close() error
2016-01-25 16:57:09 +03:00
Stats() *PoolStats
2013-07-02 15:17:31 +04:00
}
type connList struct {
cns []*conn
mx sync.Mutex
len int32 // atomic
size int32
2013-07-02 15:17:31 +04:00
}
func newConnList(size int) *connList {
return &connList{
cns: make([]*conn, 0, size),
size: int32(size),
2013-07-02 15:17:31 +04:00
}
}
func (l *connList) Len() int {
return int(atomic.LoadInt32(&l.len))
}
// Reserve reserves place in the list and returns true on success. The
// caller must add or remove connection if place was reserved.
func (l *connList) Reserve() bool {
len := atomic.AddInt32(&l.len, 1)
reserved := len <= l.size
if !reserved {
atomic.AddInt32(&l.len, -1)
2015-01-24 15:12:48 +03:00
}
return reserved
}
2015-01-24 15:12:48 +03:00
// Add adds connection to the list. The caller must reserve place first.
func (l *connList) Add(cn *conn) {
l.mx.Lock()
l.cns = append(l.cns, cn)
l.mx.Unlock()
2015-01-24 15:12:48 +03:00
}
// Remove closes connection and removes it from the list.
func (l *connList) Remove(cn *conn) error {
defer l.mx.Unlock()
l.mx.Lock()
if cn == nil {
atomic.AddInt32(&l.len, -1)
return nil
2013-07-02 15:17:31 +04:00
}
for i, c := range l.cns {
if c == cn {
l.cns = append(l.cns[:i], l.cns[i+1:]...)
atomic.AddInt32(&l.len, -1)
return cn.Close()
}
2013-07-02 15:17:31 +04:00
}
2014-05-11 18:11:55 +04:00
if l.closed() {
return nil
}
panic("conn not found in the list")
2013-07-02 15:17:31 +04:00
}
func (l *connList) Replace(cn, newcn *conn) error {
defer l.mx.Unlock()
l.mx.Lock()
for i, c := range l.cns {
if c == cn {
l.cns[i] = newcn
return cn.Close()
}
}
if l.closed() {
return newcn.Close()
}
panic("conn not found in the list")
2013-11-04 11:53:48 +04:00
}
func (l *connList) Close() (retErr error) {
l.mx.Lock()
for _, c := range l.cns {
if err := c.Close(); err != nil {
retErr = err
}
}
l.cns = nil
atomic.StoreInt32(&l.len, 0)
l.mx.Unlock()
return retErr
2015-01-31 16:20:37 +03:00
}
func (l *connList) closed() bool {
return l.cns == nil
}
2013-07-02 15:17:31 +04:00
type connPool struct {
dialer func() (*conn, error)
rl *ratelimit.RateLimiter
opt *Options
conns *connList
freeConns chan *conn
2016-01-19 19:36:40 +03:00
stats PoolStats
2014-05-11 18:11:55 +04:00
_closed int32
2015-01-17 12:56:34 +03:00
2015-12-22 16:45:03 +03:00
lastErr atomic.Value
2013-07-02 15:17:31 +04:00
}
func newConnPool(opt *Options) *connPool {
p := &connPool{
dialer: newConnDialer(opt),
2015-12-02 16:40:44 +03:00
rl: ratelimit.New(3*opt.getPoolSize(), time.Second),
opt: opt,
conns: newConnList(opt.getPoolSize()),
freeConns: make(chan *conn, opt.getPoolSize()),
2015-01-31 16:20:37 +03:00
}
if p.opt.getIdleTimeout() > 0 {
go p.reaper()
}
return p
2015-01-31 16:20:37 +03:00
}
func (p *connPool) closed() bool {
return atomic.LoadInt32(&p._closed) == 1
}
func (p *connPool) isIdle(cn *conn) bool {
return p.opt.getIdleTimeout() > 0 && time.Since(cn.UsedAt) > p.opt.getIdleTimeout()
}
2015-01-31 16:20:37 +03:00
// First returns first non-idle connection from the pool or nil if
// there are no connections.
func (p *connPool) First() *conn {
2015-01-31 16:20:37 +03:00
for {
select {
case cn := <-p.freeConns:
if p.isIdle(cn) {
var err error
cn, err = p.replace(cn)
if err != nil {
2016-02-06 13:16:09 +03:00
Logger.Printf("pool.replace failed: %s", err)
continue
}
2015-01-31 16:20:37 +03:00
}
return cn
2015-01-31 16:20:37 +03:00
default:
return nil
}
}
panic("not reached")
}
2013-07-02 15:17:31 +04:00
// wait waits for free non-idle connection. It returns nil on timeout.
func (p *connPool) wait() *conn {
deadline := time.After(p.opt.getPoolTimeout())
2015-01-31 16:20:37 +03:00
for {
select {
case cn := <-p.freeConns:
if p.isIdle(cn) {
var err error
cn, err = p.replace(cn)
if err != nil {
2016-02-06 13:16:09 +03:00
Logger.Printf("pool.replace failed: %s", err)
continue
}
2015-01-31 16:20:37 +03:00
}
return cn
2015-01-31 16:20:37 +03:00
case <-deadline:
return nil
2015-01-31 16:20:37 +03:00
}
2013-07-02 15:17:31 +04:00
}
2015-01-31 16:20:37 +03:00
panic("not reached")
2013-07-02 15:17:31 +04:00
}
2015-01-31 16:20:37 +03:00
// Establish a new connection
func (p *connPool) new() (*conn, error) {
if p.rl.Limit() {
2015-01-17 12:56:34 +03:00
err := fmt.Errorf(
2015-12-22 16:45:03 +03:00
"redis: you open connections too fast (last_error=%q)",
p.loadLastErr(),
2015-01-17 12:56:34 +03:00
)
return nil, err
}
cn, err := p.dialer()
2015-01-17 12:56:34 +03:00
if err != nil {
2015-12-22 16:45:03 +03:00
p.storeLastErr(err.Error())
return nil, err
}
return cn, nil
}
// Get returns existed connection from the pool or creates a new one.
func (p *connPool) Get() (cn *conn, isNew bool, err error) {
if p.closed() {
err = errClosed
return
2013-07-02 15:17:31 +04:00
}
atomic.AddUint32(&p.stats.Requests, 1)
2016-01-19 19:36:40 +03:00
// Fetch first non-idle connection, if available.
if cn = p.First(); cn != nil {
return
2013-11-07 18:20:15 +04:00
}
// Try to create a new one.
if p.conns.Reserve() {
isNew = true
cn, err = p.new()
2013-07-02 15:17:31 +04:00
if err != nil {
p.conns.Remove(nil)
return
2013-07-02 15:17:31 +04:00
}
p.conns.Add(cn)
return
2013-07-02 15:17:31 +04:00
}
// Otherwise, wait for the available connection.
atomic.AddUint32(&p.stats.Waits, 1)
if cn = p.wait(); cn != nil {
return
}
atomic.AddUint32(&p.stats.Timeouts, 1)
err = errPoolTimeout
return
2013-07-02 15:17:31 +04:00
}
func (p *connPool) Put(cn *conn) error {
2013-09-29 12:11:18 +04:00
if cn.rd.Buffered() != 0 {
2015-09-03 17:55:31 +03:00
b, _ := cn.rd.Peek(cn.rd.Buffered())
2016-02-06 13:16:09 +03:00
err := fmt.Errorf("connection has unread data: %q", b)
Logger.Print(err)
2015-12-22 16:45:03 +03:00
return p.Remove(cn, err)
2013-09-17 13:03:17 +04:00
}
p.freeConns <- cn
2013-07-02 15:17:31 +04:00
return nil
}
func (p *connPool) replace(cn *conn) (*conn, error) {
newcn, err := p.new()
if err != nil {
_ = p.conns.Remove(cn)
return nil, err
}
_ = p.conns.Replace(cn, newcn)
return newcn, nil
}
2015-12-22 16:45:03 +03:00
func (p *connPool) Remove(cn *conn, reason error) error {
p.storeLastErr(reason.Error())
// Replace existing connection with new one and unblock waiter.
newcn, err := p.replace(cn)
if err != nil {
return err
2013-11-07 18:20:15 +04:00
}
p.freeConns <- newcn
return nil
}
// Len returns total number of connections.
2013-07-02 15:17:31 +04:00
func (p *connPool) Len() int {
return p.conns.Len()
2013-07-02 15:17:31 +04:00
}
// FreeLen returns number of free connections.
func (p *connPool) FreeLen() int {
return len(p.freeConns)
2013-07-02 15:17:31 +04:00
}
2016-01-25 16:57:09 +03:00
func (p *connPool) Stats() *PoolStats {
stats := p.stats
stats.Requests = atomic.LoadUint32(&p.stats.Requests)
stats.Waits = atomic.LoadUint32(&p.stats.Waits)
stats.Timeouts = atomic.LoadUint32(&p.stats.Timeouts)
stats.TotalConns = uint32(p.Len())
stats.FreeConns = uint32(p.FreeLen())
2016-01-25 16:57:09 +03:00
return &stats
2016-01-19 19:36:40 +03:00
}
func (p *connPool) Close() (retErr error) {
if !atomic.CompareAndSwapInt32(&p._closed, 0, 1) {
return errClosed
2013-11-07 18:20:15 +04:00
}
2015-06-03 14:50:43 +03:00
// Wait for app to free connections, but don't close them immediately.
for i := 0; i < p.Len(); i++ {
if cn := p.wait(); cn == nil {
break
}
}
2015-06-03 14:50:43 +03:00
// Close all connections.
if err := p.conns.Close(); err != nil {
retErr = err
}
return retErr
}
2015-01-31 16:20:37 +03:00
func (p *connPool) reaper() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for _ = range ticker.C {
if p.closed() {
2014-05-11 18:11:55 +04:00
break
}
// pool.First removes idle connections from the pool and
// returns first non-idle connection. So just put returned
// connection back.
if cn := p.First(); cn != nil {
p.Put(cn)
2013-07-02 15:17:31 +04:00
}
}
}
2015-12-22 16:45:03 +03:00
func (p *connPool) storeLastErr(err string) {
p.lastErr.Store(err)
}
func (p *connPool) loadLastErr() string {
if v := p.lastErr.Load(); v != nil {
return v.(string)
}
return ""
}
2013-07-02 15:17:31 +04:00
//------------------------------------------------------------------------------
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, bool, error) {
return p.cn, false, nil
}
func (p *singleConnPool) Put(cn *conn) error {
if p.cn != cn {
panic("p.cn != cn")
}
return nil
}
2015-12-22 16:45:03 +03:00
func (p *singleConnPool) Remove(cn *conn, _ error) 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
}
2016-01-25 16:57:09 +03:00
func (p *singleConnPool) Stats() *PoolStats { return nil }
2016-01-19 19:36:40 +03:00
func (p *singleConnPool) Close() error {
return nil
}
//------------------------------------------------------------------------------
type stickyConnPool struct {
pool pool
2013-07-02 15:17:31 +04:00
reusable bool
cn *conn
closed bool
mx sync.Mutex
2013-07-02 15:17:31 +04:00
}
func newStickyConnPool(pool pool, reusable bool) *stickyConnPool {
return &stickyConnPool{
2013-07-02 15:17:31 +04:00
pool: pool,
reusable: reusable,
}
}
func (p *stickyConnPool) First() *conn {
p.mx.Lock()
cn := p.cn
p.mx.Unlock()
return cn
}
func (p *stickyConnPool) Get() (cn *conn, isNew bool, err error) {
defer p.mx.Unlock()
p.mx.Lock()
2014-06-28 15:47:37 +04:00
if p.closed {
err = errClosed
return
}
2013-07-02 15:17:31 +04:00
if p.cn != nil {
cn = p.cn
return
2013-07-02 15:17:31 +04:00
}
cn, isNew, err = p.pool.Get()
2013-07-02 15:17:31 +04:00
if err != nil {
return
2013-07-02 15:17:31 +04:00
}
p.cn = cn
return
2013-07-02 15:17:31 +04:00
}
func (p *stickyConnPool) put() (err error) {
err = p.pool.Put(p.cn)
p.cn = nil
return err
}
func (p *stickyConnPool) Put(cn *conn) error {
defer p.mx.Unlock()
p.mx.Lock()
if p.closed {
return errClosed
}
2015-11-26 18:04:26 +03:00
if p.cn != cn {
panic("p.cn != cn")
}
2013-07-02 15:17:31 +04:00
return nil
}
func (p *stickyConnPool) remove(reason error) error {
err := p.pool.Remove(p.cn, reason)
p.cn = nil
return err
}
func (p *stickyConnPool) Remove(cn *conn, reason error) error {
defer p.mx.Unlock()
p.mx.Lock()
2015-11-26 18:04:26 +03:00
if p.closed {
return errClosed
}
2014-05-11 18:11:55 +04:00
if p.cn == nil {
panic("p.cn == nil")
}
if cn != nil && p.cn != cn {
panic("p.cn != cn")
2013-07-02 15:17:31 +04:00
}
return p.remove(reason)
2014-05-11 18:11:55 +04:00
}
func (p *stickyConnPool) Len() int {
defer p.mx.Unlock()
p.mx.Lock()
2013-07-02 15:17:31 +04:00
if p.cn == nil {
return 0
}
return 1
}
func (p *stickyConnPool) FreeLen() int {
defer p.mx.Unlock()
p.mx.Lock()
2013-11-07 18:20:15 +04:00
if p.cn == nil {
return 1
2013-11-07 18:20:15 +04:00
}
return 0
2013-11-07 18:20:15 +04:00
}
2016-01-25 16:57:09 +03:00
func (p *stickyConnPool) Stats() *PoolStats { return nil }
2016-01-19 19:36:40 +03:00
func (p *stickyConnPool) Close() error {
defer p.mx.Unlock()
p.mx.Lock()
if p.closed {
return errClosed
}
p.closed = true
2013-07-02 15:17:31 +04:00
var err error
if p.cn != nil {
if p.reusable {
err = p.put()
2013-07-02 15:17:31 +04:00
} else {
2015-12-22 16:45:03 +03:00
reason := errors.New("redis: sticky not reusable connection")
err = p.remove(reason)
2013-07-02 15:17:31 +04:00
}
}
return err
}