mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Merged V2.0 to HEAD
5447: (From V1.4 5278, 5279, 5280, 5285, 5298, 5299, 5304): Hibernate session size management for large transactions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5481 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -71,6 +71,34 @@ public abstract class AlfrescoTransactionSupport
|
||||
|
||||
private static Log logger = LogFactory.getLog(AlfrescoTransactionSupport.class);
|
||||
|
||||
/**
|
||||
* @return Returns the system time when the transaction started, or -1 if there is no current transaction.
|
||||
*/
|
||||
public static long getTransactionStartTime()
|
||||
{
|
||||
/*
|
||||
* This method can be called outside of a transaction, so we can go direct to the synchronizations.
|
||||
*/
|
||||
TransactionSynchronizationImpl txnSynch =
|
||||
(TransactionSynchronizationImpl) TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
|
||||
if (txnSynch == null)
|
||||
{
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive())
|
||||
{
|
||||
// need to lazily register synchronizations
|
||||
return registerSynchronizations().getTransactionStartTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1; // not in a transaction
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return txnSynch.getTransactionStartTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique identifier associated with each transaction of each thread. Null is returned if
|
||||
* no transaction is currently active.
|
||||
@@ -428,6 +456,7 @@ public abstract class AlfrescoTransactionSupport
|
||||
*/
|
||||
private static class TransactionSynchronizationImpl extends TransactionSynchronizationAdapter
|
||||
{
|
||||
private long txnStartTime;
|
||||
private final String txnId;
|
||||
private final Set<TransactionalDao> daoServices;
|
||||
private final Set<IntegrityChecker> integrityCheckers;
|
||||
@@ -442,6 +471,7 @@ public abstract class AlfrescoTransactionSupport
|
||||
*/
|
||||
public TransactionSynchronizationImpl(String txnId)
|
||||
{
|
||||
this.txnStartTime = System.currentTimeMillis();
|
||||
this.txnId = txnId;
|
||||
daoServices = new HashSet<TransactionalDao>(3);
|
||||
integrityCheckers = new HashSet<IntegrityChecker>(3);
|
||||
@@ -450,6 +480,11 @@ public abstract class AlfrescoTransactionSupport
|
||||
resources = new HashMap<Object, Object>(17);
|
||||
}
|
||||
|
||||
public long getTransactionStartTime()
|
||||
{
|
||||
return txnStartTime;
|
||||
}
|
||||
|
||||
public String getTransactionId()
|
||||
{
|
||||
return txnId;
|
||||
|
@@ -65,15 +65,20 @@ public class AlfrescoTransactionSupportTest extends TestCase
|
||||
TransactionService transactionService = serviceRegistry.getTransactionService();
|
||||
UserTransaction txn = transactionService.getUserTransaction();
|
||||
assertNull("Thread shouldn't have a txn ID", AlfrescoTransactionSupport.getTransactionId());
|
||||
assertEquals("No transaction start time expected", -1, AlfrescoTransactionSupport.getTransactionStartTime());
|
||||
|
||||
// begine the txn
|
||||
// begin the txn
|
||||
txn.begin();
|
||||
String txnId = AlfrescoTransactionSupport.getTransactionId();
|
||||
assertNotNull("Expected thread to have a txn id", txnId);
|
||||
long txnStartTime = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
assertTrue("Expected a transaction start time", txnStartTime > 0);
|
||||
|
||||
// check that the txn id doesn't change
|
||||
// check that the txn id and time doesn't change
|
||||
String txnIdCheck = AlfrescoTransactionSupport.getTransactionId();
|
||||
assertEquals("Transaction ID changed on same thread", txnId, txnIdCheck);
|
||||
long txnStartTimeCheck = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
assertEquals("Transaction start time changed on same thread", txnStartTime, txnStartTimeCheck);
|
||||
|
||||
// begin a new, inner transaction
|
||||
{
|
||||
@@ -81,12 +86,19 @@ public class AlfrescoTransactionSupportTest extends TestCase
|
||||
|
||||
String txnIdInner = AlfrescoTransactionSupport.getTransactionId();
|
||||
assertEquals("Inner transaction not started, so txn ID should not change", txnId, txnIdInner);
|
||||
long txnStartTimeInner = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
assertEquals("Inner transaction not started, so txn start time should not change", txnStartTime, txnStartTimeInner);
|
||||
|
||||
// begin the nested txn
|
||||
txnInner.begin();
|
||||
// check the ID for the outer transaction
|
||||
txnIdInner = AlfrescoTransactionSupport.getTransactionId();
|
||||
assertNotSame("Inner txn ID must be different from outer txn ID", txnIdInner, txnId);
|
||||
// Check the time against the outer transaction
|
||||
txnStartTimeInner = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
assertTrue(
|
||||
"Inner transaction start time should be greater or equal (accuracy) to the outer's",
|
||||
txnStartTime <= txnStartTimeInner);
|
||||
|
||||
// rollback the nested txn
|
||||
txnInner.rollback();
|
||||
|
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2007 Alfresco Software Limited.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program 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
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
* As a special exception to the terms and conditions of version 2.0 of
|
||||
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||
* FLOSS exception. You should have recieved a copy of the text describing
|
||||
* the FLOSS exception, and it is also available here:
|
||||
* http://www.alfresco.com/legal/licensing
|
||||
*/
|
||||
package org.alfresco.repo.transaction;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.util.resource.MethodResourceManager;
|
||||
import org.alfresco.util.resource.MethodResourceManager.MethodStatistics;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
/**
|
||||
* This interceptor gathers basic method call statistics and calls the
|
||||
* {@link org.alfresco.util.resource.MethodResourceManager resource managers} for
|
||||
* further processing. The resource managers are called <i>after</i> the method
|
||||
* invocations, but it doesn't matter too much since they are called on a time
|
||||
* frequency basis.
|
||||
* <p>
|
||||
* It acts as a sampling tool, but doesn't make any decisions or take any action
|
||||
* with regard system or transaction-related resources. All samples are stored
|
||||
* against the current transaction. If there is no current transaction then no
|
||||
* action will be taken with respect to the resource management.
|
||||
* <p>
|
||||
* The default is to activate after 10s and call through every 5s.
|
||||
* <p>
|
||||
* This class supports both interceptor-based calling as well as manual calling.
|
||||
* Long-running processes can call an this manually on every iteration and
|
||||
* get the same behaviour of regular calls to the resouce managers.
|
||||
*
|
||||
* @see org.alfresco.util.resource.MethodResourceManager
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class TransactionResourceInterceptor implements MethodInterceptor
|
||||
{
|
||||
private List<MethodResourceManager> methodResourceManagers;
|
||||
/** Default 10000ms (10s) */
|
||||
private long elapsedTimeBeforeActivationMillis = 10000L;
|
||||
/** Default 5000ms (5s) */
|
||||
private long resourceManagerCallFrequencyMillis = 5000L;
|
||||
/** When the last call to the resource managers was made by this instance */
|
||||
private volatile long lastCallMillis;
|
||||
|
||||
/**
|
||||
* A resource key unique to this interceptor. This avoids clashes with other instances up
|
||||
* and down the stack operating in the same transaction.
|
||||
*/
|
||||
private String resourceKey;
|
||||
|
||||
public TransactionResourceInterceptor()
|
||||
{
|
||||
resourceKey = "MethodStats" + super.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method-based resource managers that will be notified of the statistics.
|
||||
*
|
||||
* @param methodResourceManagers a list of resource managers - may be null or empty
|
||||
*/
|
||||
public void setMethodResourceManagers(List<MethodResourceManager> methodResourceManagers)
|
||||
{
|
||||
this.methodResourceManagers = methodResourceManagers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of seconds that a transaction must have been running for
|
||||
* before method sampling begins. The interceptor does nothing prior to this.
|
||||
*
|
||||
* @param elapsedTimeBeforeActivationMillis an initial idle time in milliseconds
|
||||
*/
|
||||
public void setElapsedTimeBeforeActivationMillis(long elapsedTimeBeforeActivationMillis)
|
||||
{
|
||||
this.elapsedTimeBeforeActivationMillis = elapsedTimeBeforeActivationMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the approximate time between calls to the
|
||||
* {@link #setMethodResourceManagers(List) registered resource managers}. This applies to this instance
|
||||
* of the interceptor and <u>not to the transaction</u>. If a single instance of this
|
||||
* class is used in multiple places, then the resource managers will still only get called at a steady
|
||||
* rate. This is mainly in order to streamline the interception prior to the activation phase, but suits
|
||||
* the resource managers since they get given the exact methods that were called anyway.
|
||||
*
|
||||
* @param resourceManagerCallFrequencyMillis an approximate time between calls to the resource managers
|
||||
*/
|
||||
public void setResourceManagerCallFrequencyMillis(long resourceManagerCallFrequencyMillis)
|
||||
{
|
||||
this.resourceManagerCallFrequencyMillis = resourceManagerCallFrequencyMillis;
|
||||
}
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable
|
||||
{
|
||||
if (methodResourceManagers == null || methodResourceManagers.size() == 0)
|
||||
{
|
||||
// We just ignore everything
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// Get the txn start time
|
||||
long txnStartTime = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
if (txnStartTime < 0)
|
||||
{
|
||||
// There is no transaction
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// Check if the required time has passed
|
||||
long now = System.currentTimeMillis();
|
||||
long txnElapsedTime = (now - txnStartTime);
|
||||
if (txnElapsedTime < elapsedTimeBeforeActivationMillis)
|
||||
{
|
||||
// It's not been long enough
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// We need to start timing the method calls
|
||||
Method calledMethod = invocation.getMethod();
|
||||
long beforeNs = System.nanoTime();
|
||||
Object ret = invocation.proceed();
|
||||
long deltaNs = System.nanoTime() - beforeNs;
|
||||
|
||||
// Get the method stats
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Method, MethodStatistics> methodStatsByMethod =
|
||||
(Map<Method, MethodStatistics>) AlfrescoTransactionSupport.getResource(resourceKey);
|
||||
if (methodStatsByMethod == null)
|
||||
{
|
||||
methodStatsByMethod = new HashMap<Method, MethodStatistics>(11);
|
||||
AlfrescoTransactionSupport.bindResource(resourceKey, methodStatsByMethod);
|
||||
}
|
||||
|
||||
// Update method stats
|
||||
MethodStatistics calledMethodStats = methodStatsByMethod.get(calledMethod);
|
||||
if (calledMethodStats == null)
|
||||
{
|
||||
calledMethodStats = new MethodStatistics();
|
||||
methodStatsByMethod.put(calledMethod, calledMethodStats);
|
||||
}
|
||||
calledMethodStats.accumulateNs(deltaNs);
|
||||
|
||||
// Check if we need to call the resource managers to clean up
|
||||
if ((now - lastCallMillis) >= resourceManagerCallFrequencyMillis)
|
||||
{
|
||||
for (MethodResourceManager resourceManager : methodResourceManagers)
|
||||
{
|
||||
resourceManager.manageResources(methodStatsByMethod, txnElapsedTime, calledMethod);
|
||||
}
|
||||
lastCallMillis = now;
|
||||
}
|
||||
|
||||
// Done
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative method allowing a manual call to check the resources. This is useful
|
||||
* in the cases where long running iterations don't necessarily pass through the
|
||||
* necessary API stack, or where the specific resources in hand can't be dealt with
|
||||
* blindly before and after resource management. The elapsed time should be that of the
|
||||
* iteration within the method (it is assumed that there won't be more than one).
|
||||
* <p>
|
||||
* If you have a loop in a method that doesn't call anything that can be intercepted
|
||||
* and handle safely, then get a pre-configured instance (usually from the application context)
|
||||
* and mimic the interceptor call.
|
||||
* <p>
|
||||
* You should get the <code>Method</code>, which is used for informational purposes, in a
|
||||
* single call when you calling code is loaded by the classloader. Introspecting every time
|
||||
* you wish to call this method is unnecessary.
|
||||
*
|
||||
* @param calledMethod the method that this check applies to
|
||||
* @param deltaNs the time in milliseconds that the repeated operation took
|
||||
*/
|
||||
public void performManualCheck(Method calledMethod, long deltaNs)
|
||||
{
|
||||
/*
|
||||
* This is mainly duplicated code, but it can be heavily used so nice patterns
|
||||
* are not preferable to speed of execution.
|
||||
*/
|
||||
|
||||
if (methodResourceManagers == null || methodResourceManagers.size() == 0)
|
||||
{
|
||||
// We just ignore everything
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the txn start time
|
||||
long txnStartTime = AlfrescoTransactionSupport.getTransactionStartTime();
|
||||
if (txnStartTime < 0)
|
||||
{
|
||||
// There is no transaction
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the required time has passed
|
||||
long now = System.currentTimeMillis();
|
||||
long txnElapsedTime = (now - txnStartTime);
|
||||
if (txnElapsedTime < elapsedTimeBeforeActivationMillis)
|
||||
{
|
||||
// It's not been long enough
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the method stats
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Method, MethodStatistics> methodStatsByMethod =
|
||||
(Map<Method, MethodStatistics>) AlfrescoTransactionSupport.getResource(resourceKey);
|
||||
if (methodStatsByMethod == null)
|
||||
{
|
||||
methodStatsByMethod = new HashMap<Method, MethodStatistics>(11);
|
||||
AlfrescoTransactionSupport.bindResource(resourceKey, methodStatsByMethod);
|
||||
}
|
||||
|
||||
// Update method stats
|
||||
MethodStatistics calledMethodStats = methodStatsByMethod.get(calledMethod);
|
||||
if (calledMethodStats == null)
|
||||
{
|
||||
calledMethodStats = new MethodStatistics();
|
||||
methodStatsByMethod.put(calledMethod, calledMethodStats);
|
||||
}
|
||||
calledMethodStats.accumulateNs(deltaNs);
|
||||
|
||||
// Check if we need to call the resource managers to clean up
|
||||
if ((now - lastCallMillis) >= resourceManagerCallFrequencyMillis)
|
||||
{
|
||||
for (MethodResourceManager resourceManager : methodResourceManagers)
|
||||
{
|
||||
resourceManager.manageResources(methodStatsByMethod, txnElapsedTime, calledMethod);
|
||||
}
|
||||
lastCallMillis = now;
|
||||
}
|
||||
// Done
|
||||
return;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user