package org.zenoss.zep.utils; import com.google.api.client.util.ExponentialBackOff; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisDataException; import java.io.IOException; public final class JedisPoolUtil { private static Logger logger = LoggerFactory.getLogger(JedisPoolUtil.class); private final JedisPool pool; private volatile Boolean supportsEval; private volatile boolean ready; private final int maxConnectionWaitMillis; private final int maxTransactionWaitMillis; public JedisPoolUtil(JedisPool pool, int maxConnectionWaitMillis, int maxTransactionWaitMillis) { this.pool = pool; this.maxConnectionWaitMillis = maxConnectionWaitMillis; this.maxTransactionWaitMillis = maxTransactionWaitMillis; this.ready = false; } public boolean isReady() { if(!this.ready) { synchronized (this) { if(!this.ready) { Jedis jedis = null; try { jedis = this.pool.getResource(); this.ready = true; } catch (JedisConnectionException e) { // Not connected } finally { if (jedis!=null) pool.returnResource(jedis); } } } } return this.ready; } public boolean supportsEval() { Boolean result = supportsEval; if (result == null) { synchronized (this) { result = supportsEval; if (result == null) { try { useJedis(new JedisUser<Object>() { @Override public Object use(Jedis jedis) throws RedisTransactionCollision { return jedis.eval("do return end".getBytes()); } }); supportsEval = result = true; } catch (JedisDataException e) { if (e.getMessage().contains("unknown command")) { logger.info("Redis EVAL command is unsupported. Resorting to watch/multi/exec."); supportsEval = result = false; } else { throw e; } } } } } return result; } /** Provides a managed {@link Jedis} resource for temporary use. * Automatically re-executes the given block of code if there is a * connection problem (and maxConnectionAttempts is greater than zero). * Automatically re-executes the given block of code if it throws * {@link RedisTransactionCollision} (and maxTransactionAttempts is * greater than zero). */ public <T> T useJedis(JedisUser<T> user) { ExponentialBackOff backoffTracker = null; int collisions = 0; while (true) { try { return useJedisOnce(user); } catch (RedisTransactionCollision e) { collisions++; if (backoffTracker == null) { backoffTracker = new ExponentialBackOff.Builder(). setMaxElapsedTimeMillis(maxTransactionWaitMillis). build(); } long backOff; try { backOff = backoffTracker.nextBackOffMillis(); } catch (IOException ioe) { // should never happen throw new RuntimeException(ioe); } long elapsed = backoffTracker.getElapsedTimeMillis(); if (ExponentialBackOff.STOP == backOff) { if (logger.isDebugEnabled()) logger.error("Too many Redis collisions (" + collisions + "). Gave up after " + elapsed + "ms.", e); else logger.error("Too many Redis collisions (" + collisions + "). Gave up after " + elapsed + "ms."); throw e; } else { if (logger.isDebugEnabled()) logger.debug("Collision detected (" + collisions + " in " + elapsed + "ms). Backing off for " + backOff + "ms"); try { Thread.sleep(backOff); } catch (InterruptedException ie) { /* no biggie */ } } } } } private <T> T useJedisOnce(JedisUser<T> user) throws RedisTransactionCollision { ExponentialBackOff backoffTracker = null; Jedis jedis = null; int exceptions = 0; while (true) { try { if (jedis == null) jedis = pool.getResource(); T result = user.use(jedis); pool.returnResource(jedis); return result; } catch (JedisConnectionException e) { exceptions++; if (jedis != null) pool.returnBrokenResource(jedis); if (backoffTracker == null) { backoffTracker = new ExponentialBackOff.Builder(). setMaxElapsedTimeMillis(maxConnectionWaitMillis). build(); } long backOff; try { backOff = backoffTracker.nextBackOffMillis(); } catch (IOException ioe) { // should never happen throw new RuntimeException(ioe); } long elapsed = backoffTracker.getElapsedTimeMillis(); if (ExponentialBackOff.STOP == backOff) { if (logger.isDebugEnabled()) logger.error("Giving up after " + elapsed + "ms, and " + exceptions + " consecutive Redis connection exceptions. Most recent: " + e.getMessage(), e); else logger.error("Giving up after " + elapsed + "ms, and " + exceptions + " consecutive Redis connection exceptions. Most recent: " + e.getMessage()); throw e; } else { if (logger.isDebugEnabled()) logger.debug("Redis connection exception (" + exceptions + " in " + elapsed + "ms). Backing off for " + backOff + "ms"); try { Thread.sleep(backOff); } catch (InterruptedException ie) { /* no biggie */ } jedis = pool.getResource(); } } catch (RuntimeException e) { if (jedis != null) pool.returnResource(jedis); throw e; } } } }