Beispiel #1
0
// 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
}
Beispiel #2
0
// 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()
		}
	}
}
Beispiel #3
0
// 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
		}
	}
}
Beispiel #4
0
// 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)
	}
}
Beispiel #5
0
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
}