/* * IronJacamar, a Java EE Connector Architecture implementation * Copyright 2016, Red Hat Inc, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the Eclipse Public License 1.0 as * published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse * Public License for more details. * * You should have received a copy of the Eclipse Public License * along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.ironjacamar.core.connectionmanager.pool; import org.ironjacamar.core.CoreLogger; import java.util.Comparator; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jboss.logging.Logger; /** * Idle connection remover * @author <a href="mailto:jesper.pedersen@ironjacamar.org">Jesper Pedersen</a> */ public class IdleConnectionRemover { /** The logger */ private static CoreLogger logger = Logger.getMessageLogger(CoreLogger.class, IdleConnectionRemover.class.getName()); /** Thread name */ private static final String THREAD_NAME = "IronJacamar IdleConnectionRemover"; /** Singleton instance */ private static IdleConnectionRemover instance = new IdleConnectionRemover(); /** Registered pool instances */ private TreeMap<Key, ManagedConnectionPool> registeredPools; /** Executor service */ private ExecutorService executorService; /** Is the executor external */ private boolean isExternal; /** The interval */ private long interval; /** The next scan */ private long next; /** Shutdown */ private AtomicBoolean shutdown; /** Lock */ private Lock lock; /** Condition */ private Condition condition; /** * Private constructor. */ private IdleConnectionRemover() { this.registeredPools = new TreeMap<Key, ManagedConnectionPool>(new KeyComparator()); this.executorService = null; this.isExternal = false; this.interval = Long.MAX_VALUE; this.next = Long.MAX_VALUE; this.shutdown = new AtomicBoolean(false); this.lock = new ReentrantLock(true); this.condition = lock.newCondition(); } /** * Get the instance * @return The value */ public static IdleConnectionRemover getInstance() { return instance; } /** * Set the executor service * @param v The value */ public void setExecutorService(ExecutorService v) { if (v != null) { executorService = v; isExternal = true; } else { executorService = null; isExternal = false; } } /** * Start * @exception Throwable Thrown if an error occurs */ public void start() throws Throwable { if (!isExternal) { executorService = Executors.newSingleThreadExecutor(new IdleThreadFactory()); } shutdown.set(false); interval = Long.MAX_VALUE; next = Long.MAX_VALUE; executorService.execute(new IdleConnectionRemoverRunner()); } /** * Stop * @exception Throwable Thrown if an error occurs */ public void stop() throws Throwable { shutdown.set(true); if (!isExternal && executorService != null) { executorService.shutdownNow(); executorService = null; } registeredPools.clear(); } /** * Register pool for idle connection cleanup * @param mcp managed connection pool * @param mcpInterval validation interval */ public void registerPool(ManagedConnectionPool mcp, long mcpInterval) { try { lock.lock(); synchronized (registeredPools) { registeredPools.put(new Key(System.identityHashCode(mcp), System.currentTimeMillis(), mcpInterval), mcp); } if (mcpInterval > 1 && mcpInterval / 2 < interval) { interval = interval / 2; long maybeNext = System.currentTimeMillis() + interval; if (next > maybeNext && maybeNext > 0) { next = maybeNext; condition.signal(); } } } finally { lock.unlock(); } } /** * Unregister pool instance for idle connection cleanup * @param mcp pool instance */ public void unregisterPool(ManagedConnectionPool mcp) { synchronized (registeredPools) { registeredPools.values().remove(mcp); if (registeredPools.isEmpty()) interval = Long.MAX_VALUE; } } /** * Thread factory */ private static class IdleThreadFactory implements ThreadFactory { /** * {@inheritDoc} */ public Thread newThread(Runnable r) { Thread thread = new Thread(r, IdleConnectionRemover.THREAD_NAME); thread.setDaemon(true); return thread; } } /** * IdleConnectionRemoverRunner */ private class IdleConnectionRemoverRunner implements Runnable { /** * {@inheritDoc} */ public void run() { final ClassLoader oldTccl = SecurityActions.getThreadContextClassLoader(); SecurityActions.setThreadContextClassLoader(IdleConnectionRemover.class.getClassLoader()); try { lock.lock(); while (!shutdown.get()) { boolean result = instance.condition.await(instance.interval, TimeUnit.MILLISECONDS); // We only scan the ManagedConnectionPools that needs to be NavigableMap<Key, ManagedConnectionPool> entries = registeredPools.headMap(new Key(System.currentTimeMillis()), true); for (Map.Entry<Key, ManagedConnectionPool> entry : entries.entrySet()) { entry.getValue().removeIdleConnections(); entry.getKey().update(); } next = System.currentTimeMillis() + interval; if (next < 0) { next = Long.MAX_VALUE; } } } catch (InterruptedException e) { if (!shutdown.get()) logger.returningConnectionValidatorInterrupted(); } catch (RuntimeException e) { logger.connectionValidatorIgnoredUnexpectedRuntimeException(e); } catch (Exception e) { logger.connectionValidatorIgnoredUnexpectedError(e); } finally { lock.unlock(); SecurityActions.setThreadContextClassLoader(oldTccl); } } } /** * Key for ManagedConnectionPool instance */ private static class Key { private int id; private long timestamp; private long interval; /** * Constructor used for scanning * @param timestamp The timestamp */ public Key(long timestamp) { this(-1, timestamp, 0); } /** * Constructor * @param id The system identity of the ManagedConnectionPool * @param timestamp The timestamp * @param interval The interval */ public Key(int id, long timestamp, long interval) { this.id = id; this.timestamp = timestamp; this.interval = interval; } /** * Update the timestamp */ public void update() { timestamp = System.currentTimeMillis(); } /** * {@inheritDoc} */ public int hashCode() { int hash = 7; hash += 7 * id; hash += (int)(7L * timestamp); hash += (int)(7L * interval); return hash; } /** * {@inheritDoc} */ public boolean equals(Object o) { if (o == this) return true; if (o == null || !(o instanceof Key)) return false; Key k = (Key)o; return id == k.id; } /** * {@inheritDoc} */ public String toString() { return "[" + Integer.toHexString(id) + "," + timestamp + "," + interval + "]"; } } /** * Comparator for Key */ private static class KeyComparator implements Comparator<Key> { /** * Constructor */ public KeyComparator() { } /** * {@inheritDoc} */ public int compare(Key k1, Key k2) { long t1 = k1.timestamp + (k1.interval / 2); long t2 = k2.timestamp + (k2.interval / 2); if (t1 < t2) return -1; if (t1 > t2) return 1; if (k1.id != -1 && k2.id != -1) { if (k1.id < k2.id) return -1; if (k1.id > k2.id) return 1; } else { if (k1.id == -1) { return 1; } else { return -1; } } return 0; } /** * {@inheritDoc} */ public int hashCode() { return 42; } /** * {@inheritDoc} */ public boolean equals(Object o) { if (o == this) return true; if (o == null || !(o instanceof KeyComparator)) return false; return true; } } }