/* * (C) Copyright 2016 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.runtime.jtajca; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimerTask; import java.util.stream.Collectors; import javax.resource.ResourceException; import javax.transaction.TransactionManager; import org.apache.commons.logging.LogFactory; import org.apache.geronimo.connector.outbound.AbstractConnectionManager; import org.apache.geronimo.connector.outbound.ConnectionHandleInterceptor; import org.apache.geronimo.connector.outbound.ConnectionInfo; import org.apache.geronimo.connector.outbound.ConnectionInterceptor; import org.apache.geronimo.connector.outbound.ConnectionReturnAction; import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor; import org.apache.geronimo.connector.outbound.GenericConnectionManager; import org.apache.geronimo.connector.outbound.MCFConnectionInterceptor; import org.apache.geronimo.connector.outbound.PoolIdleReleaserTimer; import org.apache.geronimo.connector.outbound.SubjectInterceptor; import org.apache.geronimo.connector.outbound.SubjectSource; import org.apache.geronimo.connector.outbound.TCCLInterceptor; import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool; import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport; import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport; import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker; import org.apache.geronimo.transaction.manager.RecoverableTransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Setups a connection according to the pooling attributes, mainly duplicated from {@link GenericConnectionManager} for * injecting a connection validation interceptor. * * @since 8.3 */ public class NuxeoConnectionManager extends AbstractConnectionManager { private static final long serialVersionUID = 1L; protected static final Logger log = LoggerFactory.getLogger(NuxeoConnectionManager.class); final NuxeoConnectionTrackingCoordinator coordinator; public NuxeoConnectionManager(int activettl, NuxeoValidationSupport validationSupport, TransactionSupport transactionSupport, PoolingSupport pooling, SubjectSource subjectSource, NuxeoConnectionTrackingCoordinator connectionTracker, RecoverableTransactionManager transactionManager, String name, ClassLoader classLoader) { super(new InterceptorsImpl(validationSupport, transactionSupport, pooling, subjectSource, name, connectionTracker, transactionManager, classLoader), transactionManager, name); coordinator = connectionTracker; activemonitor = new ActiveMonitor(activettl); nosharing = new Nosharing(); } static class InterceptorsImpl implements AbstractConnectionManager.Interceptors { private final ConnectionInterceptor stack; private final ConnectionInterceptor recoveryStack; private final PoolingSupport poolingSupport; /** * Order of constructed interceptors: * <p/> * ConnectionTrackingInterceptor (connectionTracker != null) TCCLInterceptor ConnectionHandleInterceptor * ValidationHandleInterceptor TransactionCachingInterceptor (useTransactions & useTransactionCaching) * TransactionEnlistingInterceptor (useTransactions) SubjectInterceptor (realmBridge != null) * SinglePoolConnectionInterceptor or MultiPoolConnectionInterceptor LocalXAResourceInsertionInterceptor or * XAResourceInsertionInterceptor (useTransactions (&localTransactions)) MCFConnectionInterceptor */ public InterceptorsImpl(NuxeoValidationSupport validationSupport, TransactionSupport transactionSupport, PoolingSupport pooling, SubjectSource subjectSource, String name, ConnectionTracker connectionTracker, TransactionManager transactionManager, ClassLoader classLoader) { poolingSupport = pooling; // check for consistency between attributes if (subjectSource == null && pooling instanceof PartitionedPool && ((PartitionedPool) pooling).isPartitionBySubject()) { throw new IllegalStateException("To use Subject in pooling, you need a SecurityDomain"); } // Set up the interceptor stack MCFConnectionInterceptor tail = new MCFConnectionInterceptor(); ConnectionInterceptor stack = tail; stack = transactionSupport.addXAResourceInsertionInterceptor(stack, name); stack = pooling.addPoolingInterceptors(stack); if (log.isTraceEnabled()) { log.trace("Connection Manager " + name + " installed pool " + stack); } stack = validationSupport.addValidationInterceptors(stack); stack = transactionSupport.addTransactionInterceptors(stack, transactionManager); if (subjectSource != null) { stack = new SubjectInterceptor(stack, subjectSource); } if (transactionSupport.isRecoverable()) { recoveryStack = new TCCLInterceptor(stack, classLoader); } else { recoveryStack = null; } stack = new ConnectionHandleInterceptor(stack); stack = new TCCLInterceptor(stack, classLoader); if (connectionTracker != null) { stack = new ConnectionTrackingInterceptor(stack, name, connectionTracker); } tail.setStack(stack); this.stack = stack; if (log.isDebugEnabled()) { StringBuilder s = new StringBuilder("ConnectionManager Interceptor stack;\n"); stack.info(s); log.debug(s.toString()); } } @Override public ConnectionInterceptor getStack() { return stack; } @Override public ConnectionInterceptor getRecoveryStack() { return recoveryStack; } @Override public PoolingSupport getPoolingAttributes() { return poolingSupport; } } @Override public void doStop() throws Exception { if (getConnectionCount() < getPartitionMinSize()) { Thread.sleep(10); // wait for filling tasks completion } super.doStop(); } /** * * @see ActiveMonitor#killTimedoutConnections * @since 8.4 */ public List<ActiveMonitor.TimeToLive> killActiveTimedoutConnections(long clock) { return activemonitor.killTimedoutConnections(clock); } /** * * @see NuxeoConnectionTrackingCoordinator#k * @since 8.4 */ public long getKilledConnectionCount() { return activemonitor.killedCount; } final ActiveMonitor activemonitor; class ActiveMonitor implements ConnectionTracker { final int ttl; ActiveMonitor(int delay) { ttl = delay; if (ttl > 0) { scheduleCleanups(); } coordinator.addTracker(this); } final Map<ConnectionInfo, TimeToLive> ttls = new HashMap<>(); long killedCount = 0L; CleanupTask cleanup = new CleanupTask(); class CleanupTask extends TimerTask { @Override public void run() { killActiveTimedoutConnections(System.currentTimeMillis()); } } void cancelCleanups() { cleanup.cancel(); } void scheduleCleanups() { PoolIdleReleaserTimer.getTimer().scheduleAtFixedRate(cleanup, 60 * 1000, 60 * 1000); } @Override public synchronized void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, boolean reassociate) throws ResourceException { int delay = ttl(); if (delay > 0) { ttls.put(connectionInfo, new TimeToLive(connectionInfo, Thread.currentThread().getName(), System.currentTimeMillis(), delay)); } } @Override public synchronized void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) { ttls.remove(connectionInfo); } @Override public void setEnvironment(ConnectionInfo connectionInfo, String key) { return; } /** * Kill active connection that have timed out relative to the given {@code clock}. * * @return information about the killed connections * @param clock * @since 8.4 */ public synchronized List<TimeToLive> killTimedoutConnections(long clock) { List<TimeToLive> killed = new LinkedList<>(); Iterator<TimeToLive> it = ttls.values().iterator(); while (it.hasNext()) { TimeToLive ttl = it.next(); if (ttl.deadline <= clock) { ttl.killAndLog(); killed.add(ttl); it.remove(); } } return killed; } /** * Logs active connections * * * @since 8.4 */ public void log() { for (TimeToLive ttl : ttls.values()) { LogFactory.getLog(TimeToLive.class).warn(ttl, ttl.info.getTrace()); } } /** * List active connections * * * @since 8.4 */ public Set<TimeToLive> list() { return new HashSet<>(ttls.values()); } public class TimeToLive { public final ConnectionInfo info; public final String threadName; public final long obtained; public final long deadline; TimeToLive(ConnectionInfo info, String threadName, long obtained, int delay) { this.info = info; this.threadName = threadName; this.obtained = System.currentTimeMillis(); deadline = obtained + delay; } void killAndLog() { try { info.getManagedConnectionInfo().getPoolInterceptor().returnConnection(info, ConnectionReturnAction.DESTROY); } catch (Throwable error) { if (error instanceof InterruptedException) { Thread.currentThread().interrupt(); throw error; } LogFactory.getLog(NuxeoConnectionTrackingCoordinator.class) .error("Caught error while killing " + info, error); } finally { killedCount += 1; LogFactory.getLog(NuxeoConnectionTrackingCoordinator.class) .error("Killed " + message(new StringBuilder()), info.getTrace()); } } void log(long clock) { if (deadline < clock) { LogFactory.getLog(NuxeoConnectionTrackingCoordinator.class).info(message(new StringBuilder()), info.getTrace()); } else { LogFactory.getLog(NuxeoConnectionTrackingCoordinator.class).error(message(new StringBuilder()), info.getTrace()); } } public StringBuilder message(StringBuilder builder) { return builder.append(info).append(", was obtained by ").append(threadName).append(" at ") .append(new Date(obtained)).append(" and timed out at ").append(new Date(deadline)); } @Override public String toString() { return String.format("TimeToLive(%x) %s", hashCode(), message(new StringBuilder()).toString()); } } final ThreadLocal<Integer> context = new ThreadLocal<>(); public void enter(int delay) { context.set(delay); }; public void exit() { context.remove(); } int ttl() { Integer value = context.get(); if (value != null) { return value.intValue(); } return ttl; } } public int getActiveTimeoutMinutes() { return activemonitor.ttl / (60 * 1000); } public Set<ConnectionInfo> listActive() { return activemonitor.ttls.values().stream().map(ttl -> ttl.info).collect(Collectors.toSet()); } public void enterActiveMonitor(int delay) { activemonitor.enter(delay); } public void exitActiveTimedout() { activemonitor.exit(); } final Nosharing nosharing; class Nosharing implements ConnectionTracker { Nosharing() { coordinator.addTracker(this); } @Override public void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, boolean reassociate) throws ResourceException { } @Override public void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) { } final ThreadLocal<Boolean> noSharingHolder = new ThreadLocal<Boolean>(); @Override public void setEnvironment(ConnectionInfo connectionInfo, String key) { connectionInfo.setUnshareable(noSharingHolder.get() == null ? false : true); } void enter() { noSharingHolder.set(Boolean.TRUE); } void exit() { noSharingHolder.remove(); } } public void enterNoSharing() { nosharing.enter(); } public void exitNoSharing() { nosharing.exit(); } }