/* * IronJacamar, a Java EE Connector Architecture implementation * Copyright 2015, 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.stable; import org.ironjacamar.core.api.connectionmanager.pool.CapacityDecrementer; import org.ironjacamar.core.api.connectionmanager.pool.FlushMode; import org.ironjacamar.core.connectionmanager.Credential; import org.ironjacamar.core.connectionmanager.listener.ConnectionListener; import org.ironjacamar.core.connectionmanager.pool.AbstractManagedConnectionPool; import org.ironjacamar.core.connectionmanager.pool.CapacityFiller; import org.ironjacamar.core.connectionmanager.pool.CapacityRequest; import org.ironjacamar.core.connectionmanager.pool.ConnectionValidator; import org.ironjacamar.core.connectionmanager.pool.FillRequest; import org.ironjacamar.core.connectionmanager.pool.IdleConnectionRemover; import org.ironjacamar.core.connectionmanager.pool.ManagedConnectionPoolUtility; import org.ironjacamar.core.connectionmanager.pool.PoolFiller; import org.ironjacamar.core.connectionmanager.pool.capacity.DefaultCapacity; import org.ironjacamar.core.connectionmanager.pool.capacity.TimedOutDecrementer; import org.ironjacamar.core.connectionmanager.pool.capacity.TimedOutFIFODecrementer; import org.ironjacamar.core.tracer.Tracer; import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; import javax.resource.ResourceException; import javax.resource.spi.ManagedConnection; import javax.resource.spi.ManagedConnectionFactory; import javax.resource.spi.ValidatingManagedConnectionFactory; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROY; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROYED; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.FLUSH; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.FREE; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.IN_USE; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.TO_POOL; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.VALIDATION; import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.ZOMBIE; /** * The stable ManagedConnectionPool */ public class StableManagedConnectionPool extends AbstractManagedConnectionPool { /** * The associated pool */ private StablePool pool; /** * The connection listeners */ private ConcurrentLinkedDeque<ConnectionListener> listeners; /** * Constructor * * @param pool The pool * @param credential The credential */ public StableManagedConnectionPool(StablePool pool, Credential credential) { super(pool, credential); this.pool = pool; this.listeners = new ConcurrentLinkedDeque<ConnectionListener>(); if (credential.equals(pool.getPrefillCredential()) && pool.getConfiguration().isPrefill() && pool.getConfiguration().getInitialSize() > 0) { PoolFiller.fillPool(new FillRequest(this, pool.getConfiguration().getInitialSize())); } if (pool.getConfiguration().isBackgroundValidation() && pool.getConfiguration().getBackgroundValidationMillis() > 0) { //Register validation ConnectionValidator.getInstance().registerPool(this, pool.getConfiguration().getBackgroundValidationMillis()); } if (pool.getConfiguration().getIdleTimeoutMinutes() > 0) { //Register idle connection cleanup IdleConnectionRemover.getInstance().registerPool(this, pool.getConfiguration().getIdleTimeoutMinutes() * 60 * 1000L); } } /** * {@inheritDoc} */ public ConnectionListener getConnectionListener() throws ResourceException { if (pool.getLogger().isTraceEnabled()) { synchronized (this) { pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this, "getConnectionListener()", pool.getConnectionManager().getManagedConnectionFactory(), pool.getConnectionManager(), pool, pool.getConfiguration(), listeners, pool.getInternalStatistics(), credential.getSubject(), credential.getConnectionRequestInfo())); } } else if (pool.getLogger().isDebugEnabled()) { pool.getLogger().debug(ManagedConnectionPoolUtility.details("getConnectionListener()", pool.getConfiguration().getId(), getCount(IN_USE, listeners), pool.getConfiguration().getMaxSize())); } // Use request semaphore, as a fair queue across all credentials long timestamp = pool.getInternalStatistics().isEnabled() ? System.currentTimeMillis() : 0L; try { if (pool.getRequestSemaphore().tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS)) { Iterator<ConnectionListener> listenersIterator = poolIsFifo ? listeners.iterator() : listeners.descendingIterator(); while (listenersIterator.hasNext()) { ConnectionListener cl = listenersIterator.next(); if (cl.changeState(FREE, VALIDATION)) { if (pool.getInternalStatistics().isEnabled()) pool.getInternalStatistics().deltaTotalBlockingTime(System.currentTimeMillis() - timestamp); if (pool.getConfiguration().isValidateOnMatch()) { ConnectionListener result = validateConnectionListener(listeners, cl, IN_USE); if (result != null) { if (Tracer.isEnabled()) Tracer.getConnectionListener(pool.getConfiguration().getId(), this, result, true, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getJanitor().isRecording()) pool.getJanitor().registerConnectionListener(result); result.fromPool(); if (pool.getInternalStatistics().isEnabled()) { pool.getInternalStatistics().deltaInUseCount(1); pool.getInternalStatistics().deltaTotalGetTime(result.getFromPool() - timestamp); pool.getInternalStatistics().deltaTotalPoolTime(result.getFromPool() - result.getToPool()); } return result; } else { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, true, false, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getConfiguration().isUseFastFail()) break; } } else { if (cl.changeState(VALIDATION, IN_USE)) { if (Tracer.isEnabled()) Tracer.getConnectionListener(pool.getConfiguration().getId(), this, cl, true, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getJanitor().isRecording()) pool.getJanitor().registerConnectionListener(cl); cl.fromPool(); if (pool.getInternalStatistics().isEnabled()) { pool.getInternalStatistics().deltaInUseCount(1); pool.getInternalStatistics().deltaTotalGetTime(cl.getFromPool() - timestamp); pool.getInternalStatistics().deltaTotalPoolTime(cl.getFromPool() - cl.getToPool()); } return cl; } else { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, false, true, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); destroyAndRemoveConnectionListener(cl, listeners); } } } } try { ConnectionListener cl = pool.createConnectionListener(credential, this); cl.setState(IN_USE); cl.fromPool(); listeners.addLast(cl); if (Tracer.isEnabled()) Tracer.getConnectionListener(pool.getConfiguration().getId(), this, cl, true, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getJanitor().isRecording()) pool.getJanitor().registerConnectionListener(cl); return cl; } catch (ResourceException re) { throw re; } finally { prefill(); // Trigger capacity increase if (pool.getCapacity().getIncrementer() != null) CapacityFiller.schedule(new CapacityRequest(this)); } } else { if (pool.getInternalStatistics().isEnabled()) pool.getInternalStatistics().deltaWaitCount(); } } catch (Exception e) { // TODO } if (pool.getInternalStatistics().isEnabled()) pool.getInternalStatistics().deltaBlockingFailureCount(); throw new ResourceException("No ConnectionListener"); } /** * {@inheritDoc} */ public void returnConnectionListener(ConnectionListener cl, boolean kill) throws ResourceException { if (pool.getLogger().isTraceEnabled()) { synchronized (this) { pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this, "returnConnectionListener(" + Integer.toHexString(System.identityHashCode(cl)) + ", " + kill + ")", pool.getConnectionManager().getManagedConnectionFactory(), pool.getConnectionManager(), pool, pool.getConfiguration(), listeners, pool.getInternalStatistics(), credential.getSubject(), credential.getConnectionRequestInfo())); } } else if (pool.getLogger().isDebugEnabled()) { pool.getLogger().debug(ManagedConnectionPoolUtility.details( "returnConnectionListener(" + Integer.toHexString(System.identityHashCode(cl)) + ", " + kill + ")", pool.getConfiguration().getId(), getCount(IN_USE, listeners), pool.getConfiguration().getMaxSize())); } try { if (pool.getJanitor().isRecording()) pool.getJanitor().unregisterConnectionListener(cl); if (cl.getState() != DESTROYED && pool.getInternalStatistics().isEnabled()) { pool.getInternalStatistics().deltaInUseCount(-1); pool.getInternalStatistics().deltaTotalUsageTime(System.currentTimeMillis() - cl.getFromPool()); } if (!kill) { if (cl.changeState(IN_USE, TO_POOL)) { try { cl.getManagedConnection().cleanup(); cl.toPool(); if (!cl.changeState(TO_POOL, FREE)) kill = true; } catch (ResourceException re) { kill = true; } } else { kill = true; } } if (kill && cl.getState() != DESTROYED) { try { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, true, false, false, false, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); pool.destroyConnectionListener(cl); } finally { listeners.remove(cl); } } } finally { pool.getRequestSemaphore().release(); } } /** * {@inheritDoc} */ public synchronized void shutdown() { if (pool.getConfiguration().isBackgroundValidation() && pool.getConfiguration().getBackgroundValidationMillis() > 0) { ConnectionValidator.getInstance().unregisterPool(this); } if (pool.getConfiguration().getIdleTimeoutMinutes() > 0) { IdleConnectionRemover.getInstance().unregisterPool(this); } for (ConnectionListener cl : listeners) { if (cl.getState() == IN_USE) { // TODO } else if (cl.getState() == DESTROY) { // TODO } try { if (Tracer.isEnabled()) Tracer.clearConnectionListener(pool.getConfiguration().getId(), this, cl); pool.destroyConnectionListener(cl); } catch (ResourceException re) { // TODO cl.setState(ZOMBIE); } } listeners.clear(); } /** * Prefill */ @Override public void prefill() { if (credential.equals(pool.getPrefillCredential()) && pool.getConfiguration().isPrefill() && pool.getConfiguration().getMinSize() > 0 && listeners.size() < pool.getConfiguration().getMinSize()) { PoolFiller.fillPool(new FillRequest(this, pool.getConfiguration().getMinSize())); } } /** * Fill to * * @param size The size */ public void fillTo(int size) { if (size <= 0) return; if (pool.getLogger().isTraceEnabled()) { synchronized (this) { pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this, "fillTo(" + size + ")", pool.getConnectionManager().getManagedConnectionFactory(), pool.getConnectionManager(), pool, pool.getConfiguration(), listeners, pool.getInternalStatistics(), credential.getSubject(), credential.getConnectionRequestInfo())); } } else if (pool.getLogger().isDebugEnabled()) { pool.getLogger().debug(ManagedConnectionPoolUtility.details( "fillTo(" + size + ")", pool.getConfiguration().getId(), getCount(IN_USE, listeners), pool.getConfiguration().getMaxSize())); } while (!pool.isFull()) { // Get a permit - avoids a race when the pool is nearly full // Also avoids unnessary fill checking when all connections are checked out try { //TODO:statistics if (pool.getRequestSemaphore() .tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS)) { try { if (pool.isShutdown()) { return; } // We already have enough connections if (listeners.size() >= size) { return; } // Create a connection to fill the pool try { ConnectionListener cl = pool.createConnectionListener(credential, this); if (Tracer.isEnabled()) Tracer.createConnectionListener(pool.getConfiguration().getId(), this, cl, cl.getManagedConnection(), false, true, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); boolean added = false; if (listeners.size() < size) { listeners.add(cl); added = true; } if (!added) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, false, false, true, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); pool.destroyConnectionListener(cl); return; } } catch (ResourceException re) { return; } } finally { pool.getRequestSemaphore().release(); } } } catch (InterruptedException ignored) { Thread.interrupted(); //TODO:trace } } } /** * {@inheritDoc} */ public void validateConnections() { boolean anyDestroyed = false; ManagedConnectionFactory mcf = pool.getConnectionManager().getManagedConnectionFactory(); if (mcf instanceof ValidatingManagedConnectionFactory) { ValidatingManagedConnectionFactory vcf = (ValidatingManagedConnectionFactory) mcf; long timestamp = System.currentTimeMillis(); for (ConnectionListener cl : listeners) { if (cl.changeState(FREE, VALIDATION)) { if (cl.getValidated() + pool.getConfiguration().getBackgroundValidationMillis() <= timestamp) { ConnectionListener result = validateConnectionListener(listeners, cl, FREE); if (result == null) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, true, false, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); anyDestroyed = true; } } else { if (!cl.changeState(VALIDATION, FREE)) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, false, true, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); destroyAndRemoveConnectionListener(cl, listeners); } } } } } else { log.validateOnMatchNonCompliantManagedConnectionFactory(mcf.getClass().getName()); } if (anyDestroyed) prefill(); } /** * {@inheritDoc} */ public void removeIdleConnections() { long now = System.currentTimeMillis(); long timeoutSetting = pool.getConfiguration().getIdleTimeoutMinutes() * 1000L * 60; CapacityDecrementer decrementer = pool.getCapacity().getDecrementer(); if (decrementer == null || !credential.equals(pool.getPrefillCredential())) { decrementer = DefaultCapacity.DEFAULT_DECREMENTER; } if (TimedOutDecrementer.class.getName().equals(decrementer.getClass().getName()) || TimedOutFIFODecrementer.class.getName().equals(decrementer.getClass().getName())) { // Allow through each minute if (now < (lastIdleCheck + 60000L)) return; } else { // Otherwise, strict check if (now < (lastIdleCheck + timeoutSetting)) return; } lastIdleCheck = now; long timeout = now - timeoutSetting; int destroyed = 0; if (pool.getLogger().isTraceEnabled()) { synchronized (this) { pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this, "removeIdleConnections(" + timeout + ")", pool.getConnectionManager().getManagedConnectionFactory(), pool.getConnectionManager(), pool, pool.getConfiguration(), listeners, pool.getInternalStatistics(), credential.getSubject(), credential.getConnectionRequestInfo())); } } else if (pool.getLogger().isDebugEnabled()) { pool.getLogger().debug(ManagedConnectionPoolUtility.details( "removeIdleConnections(" + timeout + ")", pool.getConfiguration().getId(), getCount(IN_USE, listeners), pool.getConfiguration().getMaxSize())); } for (ConnectionListener cl : listeners) { if (cl.changeState(FREE, VALIDATION)) { if (decrementer .shouldDestroy(cl, timeout, listeners.size(), pool.getConfiguration().getMinSize(), destroyed)) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, true, false, false, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getInternalStatistics().isEnabled()) pool.getInternalStatistics().deltaTimedOut(); destroyAndRemoveConnectionListener(cl, listeners); destroyed++; } else { if (!cl.changeState(VALIDATION, FREE)) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, false, true, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); if (pool.getInternalStatistics().isEnabled()) pool.getInternalStatistics().deltaTimedOut(); destroyAndRemoveConnectionListener(cl, listeners); destroyed++; } } } } if (!pool.isShutdown()) { boolean emptyManagedConnectionPool = false; if (credential.equals(pool.getPrefillCredential()) && pool.getConfiguration().isPrefill()) { if (pool.getConfiguration().getMinSize() > 0) { prefill(); } else { emptyManagedConnectionPool = true; } } else { emptyManagedConnectionPool = true; } // Empty pool if (emptyManagedConnectionPool && listeners.isEmpty()) pool.emptyManagedConnectionPool(this); } } /** * {@inheritDoc} */ @Override public void increaseCapacity() { //Only the prefill credential MCP should be affected by the incrementer if (!credential.equals(pool.getPrefillCredential())) return; // We have already created one connection when this method is scheduled int created = 1; boolean create = true; while (create && !pool.isFull()) { try { long startWait = 0L; if (pool.getRequestSemaphore() .tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS)) { try { if (pool.isShutdown()) { return; } int currentSize = listeners.size(); create = pool.getCapacity().getIncrementer() .shouldCreate(currentSize, pool.getConfiguration().getMaxSize(), created); if (create) { try { ConnectionListener cl = pool.createConnectionListener(credential, this); if (Tracer.isEnabled()) Tracer.createConnectionListener(pool.getConfiguration().getId(), this, cl, cl.getManagedConnection(), false, false, true, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); boolean added = false; if (listeners.size() < pool.getConfiguration().getMaxSize()) { //if (trace) // log.trace("Capacity fill: cl=" + cl); listeners.add(cl); created++; added = true; } if (!added) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, true, false, false, false, true, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); pool.destroyConnectionListener(cl); return; } } catch (ResourceException re) { //log.unableFillPool(re); return; } } } finally { pool.getRequestSemaphore().release(); } } } catch (InterruptedException ignored) { Thread.interrupted(); //if (trace) // log.trace("Interrupted while requesting permit in increaseCapacity"); } } } /** * {@inheritDoc} */ public boolean isEmpty() { return listeners.isEmpty(); } /** * {@inheritDoc} */ public void flush(FlushMode mode) { // Destroy connections in the pool for (ConnectionListener cl : listeners) { switch (mode) { case ALL: { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, true, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); destroyAndRemoveConnectionListener(cl, listeners); break; } case INVALID: { if (cl.changeState(FREE, VALIDATION)) { validateConnectionListener(listeners, cl, FREE); } break; } case IDLE: { if (cl.changeState(FREE, FLUSH)) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, true, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); destroyAndRemoveConnectionListener(cl, listeners); } break; } case GRACEFULLY: { if (cl.changeState(FREE, FLUSH)) { if (Tracer.isEnabled()) Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false, false, true, false, false, false, Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null); destroyAndRemoveConnectionListener(cl, listeners); } else if (cl.getState() == IN_USE || cl.getState() == TO_POOL || cl.getState() == VALIDATION) { cl.setState(DESTROY); } break; } } } // Trigger prefill prefill(); } /** * {@inheritDoc} */ public ConnectionListener findConnectionListener(ManagedConnection mc, Object c) { return findConnectionListener(mc, c, listeners); } /** * {@inheritDoc} */ public ConnectionListener removeConnectionListener(boolean free) { return removeConnectionListener(free, listeners); } }