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:
Derek Hulley
2007-04-11 23:03:02 +00:00
parent f43d8864cb
commit d3e08db677
11 changed files with 895 additions and 30 deletions

View File

@@ -25,9 +25,11 @@
package org.alfresco.repo.domain.hibernate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -44,6 +46,7 @@ import org.alfresco.repo.domain.Store;
import org.alfresco.repo.domain.StoreKey;
import org.alfresco.repo.domain.Transaction;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
@@ -51,6 +54,7 @@ import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.GenericJDBCException;
@@ -64,7 +68,6 @@ import org.hibernate.exception.GenericJDBCException;
public class HibernateNodeTest extends BaseSpringTest
{
private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/HibernateNodeTest";
private static int i = 0;
private Store store;
private Server server;
@@ -360,6 +363,120 @@ public class HibernateNodeTest extends BaseSpringTest
txn.rollback();
}
}
/**
* This test demonstrates how entities are effectively rendered useless when the session
* is cleared. The object itself will appear to behave properly, but it is only when
* it comes to retrieving the associated values that one discovers that they were not
* persisted at all. Uncomment at <b>UNCOMMENT FOR FAILURE</b> to see the effect in action.
*/
public void testPostCommitClearIssue() throws Exception
{
// commit the transaction
setComplete();
endTransaction();
// Start a transaction explicitly
TransactionService transactionService = (TransactionService) applicationContext.getBean("transactionComponent");
UserTransaction txn = transactionService.getUserTransaction();
// We need a listener
TestPostCommitClearIssueHelper listener = new TestPostCommitClearIssueHelper();
try
{
txn.begin();
// Bind the listener
AlfrescoTransactionSupport.bindListener(listener);
// Bind a list of node IDs into the transaction
List<Long> nodeIds = new ArrayList<Long>(100);
AlfrescoTransactionSupport.bindResource("node_ids", nodeIds);
// Bind the session in, too
Session session = getSession();
AlfrescoTransactionSupport.bindResource("session", session);
// Make a whole lot of nodes with aspects and properties
for (int i = 0; i < 100; i++)
{
// make a node
Node node = new NodeImpl();
node.setStore(store);
node.setUuid(GUID.generate());
node.setTypeQName(ContentModel.TYPE_CONTENT);
Long nodeId = (Long) getSession().save(node);
// Record the ID
nodeIds.add(nodeId);
// Now flush and clear
/* UNCOMMENT FOR FAILURE */
/* flushAndClear(); */
// add some aspects to the node
Set<QName> aspects = node.getAspects();
aspects.add(ContentModel.ASPECT_AUDITABLE);
// add some properties
Map<QName, PropertyValue> properties = node.getProperties();
properties.put(ContentModel.PROP_NAME, new PropertyValue(DataTypeDefinition.TEXT, "ABC"));
}
// Commit the transaction
txn.commit();
}
catch (Throwable e)
{
try { txn.rollback(); } catch (Throwable ee) {}
}
// Did the listener find any issues?
if (listener.err != null)
{
fail(listener.err);
}
}
/** Helper class to test entities during transaction wind-down */
private class TestPostCommitClearIssueHelper extends TransactionListenerAdapter
{
public String err = null;
@SuppressWarnings("unchecked")
@Override
public void beforeCommit(boolean readOnly)
{
// Get the session
Session session = (Session) AlfrescoTransactionSupport.getResource("session");
// Get the node IDs
List<Long> nodeIds = (List<Long>) AlfrescoTransactionSupport.getResource("node_ids");
// Check each node for the aspects and properties required
int incorrectAspectCount = 0;
int incorrectPropertyCount = 0;
for (Long nodeId : nodeIds)
{
Node node = (Node) session.get(NodeImpl.class, nodeId);
Set<QName> aspects = node.getAspects();
Map<QName, PropertyValue> properties = node.getProperties();
if (!aspects.contains(ContentModel.ASPECT_AUDITABLE))
{
// Missing the aspect
incorrectAspectCount++;
}
if (!properties.containsKey(ContentModel.PROP_NAME))
{
// Missing property
incorrectPropertyCount++;
}
}
// What is the outcome?
if (incorrectAspectCount > 0 || incorrectPropertyCount > 0)
{
this.err =
"Checked " + nodeIds.size() + " nodes and found: \n" +
" " + incorrectAspectCount + " missing aspects and \n" +
" " + incorrectPropertyCount + " missing properties.";
}
// Force a rollback anyway, just to stop an explosion of data
throw new RuntimeException("ROLLBACK");
}
}
/**
* Create some simple parent-child relationships and flush them. Then read them back in without

View File

@@ -0,0 +1,95 @@
/*
* 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.domain.hibernate;
import java.lang.reflect.Method;
import java.util.Map;
import org.alfresco.util.resource.MethodResourceManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.stat.SessionStatistics;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/**
* A Hibernate-specific resource manager that ensures that the current <code>Session</code>'s
* entity count doesn't exceed a given threshold.
* <p/>
* <b>NOTE: VERY IMPORTANT</b><br/>
* Do not, under any circumstances, attach an instance of this class to an API that
* passes stateful objects back and forth. There must be no <code>Session</code>-linked
* objects up the stack from where this instance resides. Failure to observe this will
* most likely result in data loss of a sporadic nature.
*
* @see org.alfresco.repo.domain.hibernate.HibernateNodeTest#testPostCommitClearIssue()
*
* @author Derek Hulley
*/
public class SessionSizeResourceManager extends HibernateDaoSupport implements MethodResourceManager
{
private static Log logger = LogFactory.getLog(SessionSizeResourceManager.class);
/** Default 1000 */
private int threshold = 1000;
/**
* Set the {@link Session#clear()} threshold. If the number of entities and collections in the
* current session exceeds this number, then the session will be cleared. Have you read the
* disclaimer?
*
* @param threshold the maximum number of entities and associations to keep in memory
*
* @see #threshold
*/
public void setThreshold(int threshold)
{
this.threshold = threshold;
}
public void manageResources(
Map<Method, MethodStatistics> methodStatsByMethod,
long transactionElapsedTimeNs,
Method currentMethod)
{
Session session = getSession(false);
SessionStatistics stats = session.getStatistics();
int entityCount = stats.getEntityCount();
int collectionCount = stats.getCollectionCount();
if ((entityCount + collectionCount) > threshold)
{
session.flush();
session.clear();
if (logger.isDebugEnabled())
{
String msg = String.format(
"Cleared %5d entities and %5d collections from Hibernate Session",
entityCount,
collectionCount);
logger.debug(msg);
}
}
}
}

View File

@@ -36,6 +36,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.transaction.UserTransaction;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.dictionary.DictionaryComponent;
@@ -1042,12 +1044,6 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
ASSOC_TYPE_QNAME_TEST_CHILDREN,
QName.createQName("pathA"),
TYPE_QNAME_TEST_MULTIPLE_TESTER).getChildRef();
// commit as we will be breaking the transaction in the test
setComplete();
endTransaction();
// each of these tests will be in a new transaction started by the NodeService
ArrayList<String> values = new ArrayList<String>(1);
values.add("ABC");
values.add("DEF");
@@ -1062,15 +1058,26 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
nodeService.setProperty(nodeRef, PROP_QNAME_ANY_PROP_MULTIPLE, values);
nodeService.setProperty(nodeRef, undeclaredPropQName, "ABC");
nodeService.setProperty(nodeRef, undeclaredPropQName, values);
// this should fail as we are passing multiple values into a non-any that is multiple=false
// commit as we will be breaking the transaction in the next test
setComplete();
endTransaction();
UserTransaction txn = transactionService.getUserTransaction();
try
{
txn.begin();
// this should fail as we are passing multiple values into a non-any that is multiple=false
nodeService.setProperty(nodeRef, PROP_QNAME_STRING_PROP_SINGLE, values);
}
catch (DictionaryException e)
{
// expected
}
finally
{
try { txn.rollback(); } catch (Throwable e) {}
}
}
/**

View File

@@ -0,0 +1,146 @@
/*
* 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.node.db.hibernate;
import java.lang.reflect.Method;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.node.BaseNodeServiceTest;
import org.alfresco.repo.node.db.DbNodeServiceImpl;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionResourceInterceptor;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
/**
* Tests the session size limiters in the context of a full stack.
*
* @see org.alfresco.util.resource.MethodResourceManager
* @see org.alfresco.repo.transaction.TransactionResourceInterceptor
* @see org.alfresco.repo.domain.hibernate.SessionSizeResourceManager
*
* @author Derek Hulley
*/
public class SessionSizeManagementTest extends BaseNodeServiceTest
{
private TransactionResourceInterceptor interceptor;
private Method createNodesMethod;
public SessionSizeManagementTest()
{
try
{
Class clazz = SessionSizeManagementTest.class;
createNodesMethod = clazz.getMethod(
"createNodes",
new Class[] {NodeService.class, Integer.TYPE, Boolean.TYPE});
}
catch (Exception e)
{
throw new RuntimeException("Instantiation failed", e);
}
}
/**
* Get the config locations
*
* @return an array containing the config locations
*/
protected String[] getConfigLocations()
{
return new String[] {"session-size-test-context.xml"};
}
@Override
protected NodeService getNodeService()
{
NodeService nodeService = (NodeService) applicationContext.getBean("testSessionSizeDbNodeService");
return nodeService;
}
@Override
protected void onSetUpInTransaction() throws Exception
{
super.onSetUpInTransaction();
// Get the interceptor for manual testing
interceptor = (TransactionResourceInterceptor) applicationContext.getBean("testSessionSizeResourceInterceptor");
}
/** Helper to create a given number of nodes using the provided service */
public void createNodes(NodeService nodeService, int count, boolean manualFlush)
{
for (int i = 0; i < count; i++)
{
long beforeNs = System.nanoTime();
nodeService.createNode(
rootNodeRef,
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.ALFRESCO_URI, "child-" + i),
ContentModel.TYPE_FOLDER);
long deltaNs = System.nanoTime() - beforeNs;
// Perform manual flush if necessary
if (manualFlush)
{
interceptor.performManualCheck(createNodesMethod, deltaNs);
}
}
}
private static final int LOAD_COUNT = 1000;
/**
* Create a bunch of nodes and see that the auto-clear is working
*/
public synchronized void testBulkLoad() throws Exception
{
NodeService nodeService = getNodeService();
createNodes(nodeService, LOAD_COUNT, false);
// We can't check the session size as this is dependent on machine speed
// Now flush integrity to be sure things are not broken
AlfrescoTransactionSupport.flush();
}
/**
* Create a bunch of nodes and see that the manual clearing is working. The
* original node service is used for this.
*/
public synchronized void testManualOperation() throws Exception
{
NodeService nodeService = (NodeService) applicationContext.getBean("dbNodeServiceImpl");
if (!(nodeService instanceof DbNodeServiceImpl))
{
fail("This test requires the unwrapped raw DbNodeServiceImpl");
}
createNodes(nodeService, LOAD_COUNT, true);
// Check the session size
int entityCount = getSession().getStatistics().getEntityCount();
assertTrue("Manual flush: Entity count should be less than " + LOAD_COUNT, entityCount < LOAD_COUNT);
// Now flush integrity to be sure things are not broken
AlfrescoTransactionSupport.flush();
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.util.resource;
import java.lang.reflect.Method;
import java.util.Map;
/**
* A controller of system or in-transaction resources. Given a few statistics
* regarding a method's call history, and using whatever other measurements
* are needed, implementations will decide whether and how to clear up
* sufficient system resources.
*
* @author Derek Hulley
*/
public interface MethodResourceManager
{
/**
* Helper class to carry basic method call statistics.
*/
public static class MethodStatistics
{
private long callCount;
private long accumulatedTimeNs;
public void accumulateNs(long durationNs)
{
accumulatedTimeNs += durationNs;
callCount++;
}
public long getAccumulatedTimeNs()
{
return accumulatedTimeNs;
}
public long getCallCount()
{
return callCount;
}
/**
* @return Returns the average call time in nanoseconds
*/
public double getAverageCallTimeNs()
{
if (callCount == 0)
{
return 0.0D;
}
return (double) accumulatedTimeNs / (double) callCount;
}
}
/**
* Check and free any required resources for an imminent. Details of the
* current transaction and some gathered information about previous calls
* to associated methods is also provided.
*
* @param methodStatsByMethod all known methods and their basic call stats
* @param transactionElapsedTimeNs the elapsed time in the current transaction
* @param currentMethod the method about to be called
*/
public void manageResources(
Map<Method, MethodStatistics> methodStatsByMethod,
long transactionElapsedTimeNs,
Method currentMethod);
}

View File

@@ -0,0 +1,72 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<!-- Check that the resource interceptor is working -->
<beans>
<import resource="classpath:alfresco/application-context.xml"/>
<bean id="testSessionSizeResourceInterceptor" class="org.alfresco.repo.transaction.TransactionResourceInterceptor" >
<property name="methodResourceManagers">
<list>
<ref bean="sessionSizeResourceManager"></ref>
</list>
</property>
<property name="elapsedTimeBeforeActivationMillis">
<value>10000</value>
</property>
<property name="resourceManagerCallFrequencyMillis">
<value>5000</value>
</property>
</bean>
<bean id="sessionSizeResourceManager" class="org.alfresco.repo.domain.hibernate.SessionSizeResourceManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
<property name="threshold">
<value>2000</value>
</property>
</bean>
<bean id="testSessionSizeDbNodeService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.alfresco.service.cmr.repository.NodeService</value>
</property>
<property name="target">
<ref bean="dbNodeService" />
</property>
<property name="interceptorNames">
<list>
<value>testSessionSizeResourceInterceptor</value>
</list>
</property>
</bean>
<!-- Point the IntegrityChecker to the correct NodeService -->
<bean id="integrityChecker" class="org.alfresco.repo.node.integrity.IntegrityChecker" init-method="init">
<property name="policyComponent">
<ref bean="policyComponent"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="nodeService">
<ref bean="testSessionSizeDbNodeService" />
</property>
<property name="enabled">
<value>true</value>
</property>
<property name="traceOn">
<value>false</value>
</property>
<property name="failOnViolation" >
<value>true</value>
</property>
<property name="maxErrorsPerTransaction" >
<value>5</value>
</property>
</bean>
</beans>