// NewCustom is like New except you can specify a DialFunc which will be // used when creating new connections for the pool. The common use-case is to do // authentication for new connections. func NewCustom(network, addr string, size int, df DialFunc) (*Pool, error) { var client *redis.Client var err error pool := make([]*redis.Client, 0, size) for i := 0; i < size; i++ { client, err = df(network, addr) if err != nil { for _, client = range pool { client.Close() } pool = pool[0:] break } pool = append(pool, client) } p := Pool{ Network: network, Addr: addr, pool: make(chan *redis.Client, len(pool)), df: df, } for i := range pool { p.pool <- pool[i] } return &p, err }
// Put returns a client back to the pool. If the pool is full the client is // closed instead. If the client is already closed (due to connection failure or // what-have-you) it will not be put back in the pool func (p *Pool) Put(conn *redis.Client) { if conn.LastCritical == nil { select { case p.pool <- conn: default: conn.Close() } } }
// Empty removes and calls Close() on all the connections currently in the pool. // Assuming there are no other connections waiting to be Put back this method // effectively closes and cleans up the pool. func (p *Pool) Empty() { var conn *redis.Client for { select { case conn = <-p.pool: conn.Close() default: return } } }
// Put putss the connection back in its pool. To be used alongside any of the // Get* methods once use of the redis.Client is done func (c *Cluster) Put(conn *redis.Client) { c.callCh <- func(c *Cluster) { p := c.pools[conn.Addr] if p == nil { conn.Close() return } p.Put(conn) } }
func (c *Cluster) clientCmd( client *redis.Client, cmd string, args []interface{}, ask bool, tried map[string]bool, haveReset bool, ) *redis.Resp { var err error var r *redis.Resp defer c.Put(client) if ask { r = client.Cmd("ASKING") ask = false } // If we asked and got an error, we continue on with error handling as we // would normally do. If we didn't ask or the ask succeeded we do the // command normally, and see how that goes if r == nil || r.Err == nil { r = client.Cmd(cmd, args...) } if err = r.Err; err == nil { return r } // At this point we have some kind of error we have to deal with. The above // code is what will be run 99% of the time and is pretty streamlined, // everything after this point is allowed to be hairy and gross haveTriedBefore := haveTried(tried, client.Addr) tried = justTried(tried, client.Addr) // Deal with network error if r.IsType(redis.IOErr) { // If this is the first time trying this node, try it again if !haveTriedBefore { if client, try2err := c.getConn("", client.Addr); try2err == nil { return c.clientCmd(client, cmd, args, false, tried, haveReset) } } // Otherwise try calling Reset() and getting a random client if !haveReset { if resetErr := c.Reset(); resetErr != nil { return errorRespf("Could not get cluster info: %s", resetErr) } client, getErr := c.getConn("", "") if getErr != nil { return errorResp(getErr) } return c.clientCmd(client, cmd, args, false, tried, true) } // Otherwise give up and return the most recent error return r } // Here we deal with application errors that are either MOVED or ASK msg := err.Error() moved := strings.HasPrefix(msg, "MOVED ") ask = strings.HasPrefix(msg, "ASK ") if moved || ask { _, addr := redirectInfo(msg) c.callCh <- func(c *Cluster) { select { case c.MissCh <- struct{}{}: default: } } // If we've already called Reset and we're getting MOVED again than the // cluster is having problems, likely telling us to try a node which is // not reachable. Not much which can be done at this point if haveReset { return errorRespf("Cluster doesn't make sense, %s might be gone", addr) } if resetErr := c.Reset(); resetErr != nil { return errorRespf("Could not get cluster info: %s", resetErr) } haveReset = true // At this point addr is whatever redis told us it should be. However, // if we can't get a connection to it we'll never actually mark it as // tried, resulting in an infinite loop. Here we mark it as tried // regardless of if it actually was or not tried = justTried(tried, addr) client, getErr := c.getConn("", addr) if getErr != nil { return errorResp(getErr) } return c.clientCmd(client, cmd, args, ask, tried, haveReset) } // It's a normal application error (like WRONG KEY TYPE or whatever), return // that to the client return r }