/* * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.nuxeo.ecm.core.redis; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import redis.clients.jedis.Client; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisMonitor; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; import redis.clients.util.Pool; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class RedisPoolExecutor extends RedisAbstractExecutor { private static final Log log = LogFactory.getLog(RedisPoolExecutor.class); private Thread monitorThread; protected Pool<Jedis> pool; protected final ThreadLocal<Jedis> holder = new ThreadLocal<>(); public RedisPoolExecutor(Pool<Jedis> pool) { this.pool = pool; } @Override public <T> T execute(RedisCallable<T> callable) throws JedisException { { // re-entrance Jedis jedis = holder.get(); if (jedis != null) { return callable.call(jedis); } } if (monitorThread != null) { log.debug(String.format("Redis pool state before getting a conn: active: %d, idle: %s", pool.getNumActive(), pool.getNumIdle())); } Jedis jedis = pool.getResource(); if (monitorThread != null) { log.debug("Using conn: " + jedis.getClient().getSocket().getLocalPort()); } holder.set(jedis); boolean brokenResource = false; try { return callable.call(jedis); } catch (JedisConnectionException cause) { brokenResource = true; throw cause; } finally { holder.remove(); // a disconnected resournce must be marked as broken // this happens when the monitoring is stopped if (brokenResource || !jedis.isConnected()) { pool.returnBrokenResource(jedis); } else { pool.returnResource(jedis); } } } @Override public Pool<Jedis> getPool() { return pool; } @Override public void startMonitor() { CountDownLatch monitorLatch = new CountDownLatch(1); monitorThread = new Thread(new Runnable() { @Override public void run() { log.debug("Starting monitor thread"); execute(jedis -> { jedis.monitor(new JedisMonitor() { @Override public void proceed(Client client) { monitorLatch.countDown(); super.proceed(client); } @Override public void onCommand(String command) { if (Thread.currentThread().isInterrupted()) { // The only way to get out of this thread jedis.disconnect(); } else { log.debug(command); } } }); log.debug("Monitor thread stopped"); return null; }); } }); monitorThread.setName("Nuxeo-Redis-Monitor"); monitorThread.start(); try { if (! monitorLatch.await(5, TimeUnit.SECONDS)) { log.error("Failed to init Redis moniotring"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } @Override public void stopMonitor() { if (monitorThread != null) { log.debug("Stoping monitor"); monitorThread.interrupt(); monitorThread = null; } } }