ActionTrackingService work

Initial cancel support, and some duplicate instance work (mostly updating tests to handle it coming along). Duplicate id assignment work still remains


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21340 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2010-07-21 15:49:19 +00:00
parent 0442d3564d
commit 17bcf56a93
6 changed files with 156 additions and 20 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.action;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.action.CancellableAction;
/**
* The Exception thrown when a {@link CancellableAction} detects
* that a cancel was requested, and needs the transaction it
* is in to be wound back as part of the cancellation.
*/
public class ActionCancelledException extends AlfrescoRuntimeException
{
public ActionCancelledException(CancellableAction action)
{
super(action.toString());
}
}

View File

@@ -106,6 +106,16 @@ public class ActionImpl extends ParameterizedItemImpl implements Action
*/ */
private List<ActionCondition> actionConditions = new ArrayList<ActionCondition>(); private List<ActionCondition> actionConditions = new ArrayList<ActionCondition>();
/**
* When there is more than one instance of the
* action executing, both with the same ID,
* which one is this?
* This crops up most often with persisted
* actions, with two copies running, one on
* each of two different target nodes.
*/
private int executionInstance = -1;
/** /**
* When the action started executing, * When the action started executing,
* or null if it hasn't yet. * or null if it hasn't yet.
@@ -466,6 +476,27 @@ public class ActionImpl extends ParameterizedItemImpl implements Action
getParameterValues().putAll(values); getParameterValues().putAll(values);
} }
/**
* When there is more than one instance of the
* action executing, both with the same ID,
* which one is this?
* This crops up most often with persisted
* actions, with two copies running, one on
* each of two different target nodes.
*/
public int getExecutionInstance()
{
return executionInstance;
}
/**
* Called by the ActionService when the action
* begins running.
*/
public void setExecutionInstance(int instance) {
executionInstance = instance;
}
public Date getExecutionStartDate() { public Date getExecutionStartDate() {
return executionStartDate; return executionStartDate;
} }

View File

@@ -795,6 +795,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action"); final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action");
assertNotNull(sleepAction); assertNotNull(sleepAction);
sleepAction.setSleepMs(10);
final int actionSubmissonCount = 4; // Rather arbitrary count. final int actionSubmissonCount = 4; // Rather arbitrary count.
for (int i = 0; i < actionSubmissonCount; i ++) for (int i = 0; i < actionSubmissonCount; i ++)
@@ -883,7 +884,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
postAsyncActionTest( postAsyncActionTest(
this.transactionService, this.transactionService,
1000, 1000l,
10, 10,
new AsyncTest() new AsyncTest()
{ {
@@ -1344,7 +1345,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
boolean cancelled = actionTrackingService.isCancellationRequested(ca); boolean cancelled = actionTrackingService.isCancellationRequested(ca);
if(cancelled) if(cancelled)
{ {
throw new RuntimeException("Cancelled!"); throw new ActionCancelledException(ca);
} }
} }
} }

View File

@@ -98,6 +98,11 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
} }
/** Used by unit tests only */
protected void resetNextExecutionId() {
this.nextExecutionId = 1;
}
public void recordActionPending(Action action) public void recordActionPending(Action action)
{ {
@@ -139,6 +144,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
// TODO assign it a (unique) execution ID // TODO assign it a (unique) execution ID
// (Keep checking to see if the key is used as we // (Keep checking to see if the key is used as we
// increase nextExecutionId until it isn't) // increase nextExecutionId until it isn't)
((ActionImpl)action).setExecutionInstance(nextExecutionId++); // TODO
String key = generateCacheKey(action); String key = generateCacheKey(action);
// Put it into the cache // Put it into the cache
@@ -153,13 +159,31 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
public void recordActionFailure(Action action, Throwable exception) public void recordActionFailure(Action action, Throwable exception)
{ {
if (logger.isDebugEnabled() == true) if (logger.isDebugEnabled() == true)
{
if(exception instanceof ActionCancelledException)
{
logger.debug("Will shortly record completed cancellation of action " + action);
}
else
{ {
logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage()); logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage());
} }
}
// Record when it finished
((ActionImpl)action).setExecutionEndDate(new Date()); ((ActionImpl)action).setExecutionEndDate(new Date());
// Record it as Failed or Cancelled, depending on the exception
if(exception instanceof ActionCancelledException)
{
((ActionImpl)action).setExecutionStatus(ActionStatus.Cancelled);
((ActionImpl)action).setExecutionFailureMessage(null);
}
else
{
((ActionImpl)action).setExecutionStatus(ActionStatus.Failed); ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed);
((ActionImpl)action).setExecutionFailureMessage(exception.getMessage()); ((ActionImpl)action).setExecutionFailureMessage(exception.getMessage());
}
// Remove it from the cache, as it's no longer running // Remove it from the cache, as it's no longer running
String key = generateCacheKey(action); String key = generateCacheKey(action);
@@ -334,7 +358,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
return return
action.getActionDefinitionName() + cacheKeyPartSeparator + action.getActionDefinitionName() + cacheKeyPartSeparator +
action.getId() + cacheKeyPartSeparator + action.getId() + cacheKeyPartSeparator +
"1"//action.getExecutionInstance // TODO ((ActionImpl)action).getExecutionInstance()
; ;
} }
protected static String generateCacheKey(ExecutionSummary summary) protected static String generateCacheKey(ExecutionSummary summary)
@@ -381,7 +405,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
return new ExecutionSummary( return new ExecutionSummary(
action.getActionDefinitionName(), action.getActionDefinitionName(),
action.getId(), action.getId(),
1 // TODO ((ActionImpl)action).getExecutionInstance()
); );
} }
} }

View File

@@ -113,6 +113,10 @@ public class ActionTrackingServiceImplTest extends TestCase
executingActionsCache.remove(key); executingActionsCache.remove(key);
} }
// Reset the execution instance IDs, so we
// can predict what they'll be
((ActionTrackingServiceImpl)actionTrackingService).resetNextExecutionId();
// Register the test executor, if needed // Register the test executor, if needed
SleepActionExecuter.registerIfNeeded(ctx); SleepActionExecuter.registerIfNeeded(ctx);
} }
@@ -120,10 +124,13 @@ public class ActionTrackingServiceImplTest extends TestCase
/** Creating cache keys */ /** Creating cache keys */
public void testCreateCacheKeys() throws Exception public void testCreateCacheKeys() throws Exception
{ {
Action action = createWorkingSleepAction("1234"); ActionImpl action = (ActionImpl)createWorkingSleepAction("1234");
assertEquals("sleep-action", action.getActionDefinitionName()); assertEquals("sleep-action", action.getActionDefinitionName());
assertEquals("1234", action.getId()); assertEquals("1234", action.getId());
// assertNull(action.getExecutionInstance()); // TODO assertEquals(-1, action.getExecutionInstance());
// Give it a predictable execution instance
action.setExecutionInstance(1);
// From an action // From an action
String key = ActionTrackingServiceImpl.generateCacheKey(action); String key = ActionTrackingServiceImpl.generateCacheKey(action);
@@ -138,8 +145,11 @@ public class ActionTrackingServiceImplTest extends TestCase
/** Creating ExecutionDetails and ExecutionSummary */ /** Creating ExecutionDetails and ExecutionSummary */
public void testExecutionDetailsSummary() throws Exception public void testExecutionDetailsSummary() throws Exception
{ {
// Create the ExecutionSummary from an action // Create an action with a known execution instance
Action action = createWorkingSleepAction("1234"); Action action = createWorkingSleepAction("1234");
((ActionImpl)action).setExecutionInstance(1);
// Create the ExecutionSummary from an action
String key = ActionTrackingServiceImpl.generateCacheKey(action); String key = ActionTrackingServiceImpl.generateCacheKey(action);
ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action);
@@ -161,10 +171,15 @@ public class ActionTrackingServiceImplTest extends TestCase
assertEquals(1, d.getExecutionInstance()); assertEquals(1, d.getExecutionInstance());
assertEquals(null, d.getPersistedActionRef()); assertEquals(null, d.getPersistedActionRef());
assertEquals(null, d.getStartedAt()); assertEquals(null, d.getStartedAt());
// TODO Check machine details
} }
// Running an action gives it an execution ID /** Running an action gives it an execution ID */
public void testExecutionInstanceAssignment()
{
// TODO // TODO
}
/** /**
* The correct things happen with the cache * The correct things happen with the cache
@@ -181,10 +196,12 @@ public class ActionTrackingServiceImplTest extends TestCase
// Can complete or fail, won't be there // Can complete or fail, won't be there
actionTrackingService.recordActionComplete(action); actionTrackingService.recordActionComplete(action);
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Completed, action.getExecutionStatus()); assertEquals(ActionStatus.Completed, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key)); assertEquals(null, executingActionsCache.get(key));
actionTrackingService.recordActionFailure(action, new Exception("Testing")); actionTrackingService.recordActionFailure(action, new Exception("Testing"));
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Failed, action.getExecutionStatus()); assertEquals(ActionStatus.Failed, action.getExecutionStatus());
assertEquals("Testing", action.getExecutionFailureMessage()); assertEquals("Testing", action.getExecutionFailureMessage());
assertEquals(null, executingActionsCache.get(key)); assertEquals(null, executingActionsCache.get(key));
@@ -192,12 +209,14 @@ public class ActionTrackingServiceImplTest extends TestCase
// Pending won't add it in either // Pending won't add it in either
actionTrackingService.recordActionPending(action); actionTrackingService.recordActionPending(action);
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Pending, action.getExecutionStatus()); assertEquals(ActionStatus.Pending, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key)); assertEquals(null, executingActionsCache.get(key));
// Run it, will go in // Run it, will go into the cache
actionTrackingService.recordActionExecuting(action); actionTrackingService.recordActionExecuting(action);
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Running, action.getExecutionStatus()); assertEquals(ActionStatus.Running, action.getExecutionStatus());
assertNotNull(null, executingActionsCache.get(key)); assertNotNull(null, executingActionsCache.get(key));
@@ -213,14 +232,17 @@ public class ActionTrackingServiceImplTest extends TestCase
// Completion removes it // Completion removes it
actionTrackingService.recordActionComplete(action); actionTrackingService.recordActionComplete(action);
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Completed, action.getExecutionStatus()); assertEquals(ActionStatus.Completed, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key)); assertEquals(null, executingActionsCache.get(key));
// Failure removes it // Failure removes it
actionTrackingService.recordActionExecuting(action); actionTrackingService.recordActionExecuting(action);
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertNotNull(null, executingActionsCache.get(key)); assertNotNull(null, executingActionsCache.get(key));
actionTrackingService.recordActionFailure(action, new Exception("Testing")); actionTrackingService.recordActionFailure(action, new Exception("Testing"));
key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Failed, action.getExecutionStatus()); assertEquals(ActionStatus.Failed, action.getExecutionStatus());
assertEquals("Testing", action.getExecutionFailureMessage()); assertEquals("Testing", action.getExecutionFailureMessage());
assertEquals(null, executingActionsCache.get(key)); assertEquals(null, executingActionsCache.get(key));
@@ -254,6 +276,10 @@ public class ActionTrackingServiceImplTest extends TestCase
Thread.sleep(150); Thread.sleep(150);
// Will get an execution instance id, so a new key
key = ActionTrackingServiceImpl.generateCacheKey(action);
// Check it's in the cache // Check it's in the cache
System.out.println("Checking the cache for " + key); System.out.println("Checking the cache for " + key);
assertNotNull(executingActionsCache.get(key)); assertNotNull(executingActionsCache.get(key));
@@ -309,6 +335,10 @@ public class ActionTrackingServiceImplTest extends TestCase
Thread.sleep(150); Thread.sleep(150);
// Will get an execution instance id, so a new key
key = ActionTrackingServiceImpl.generateCacheKey(action);
// Check it's in the cache // Check it's in the cache
System.out.println("Checking the cache for " + key); System.out.println("Checking the cache for " + key);
assertNotNull(executingActionsCache.get(key)); assertNotNull(executingActionsCache.get(key));
@@ -521,6 +551,9 @@ public class ActionTrackingServiceImplTest extends TestCase
txn.commit(); txn.commit();
Thread.sleep(150); Thread.sleep(150);
// Get the updated key, and check
key3 = ActionTrackingServiceImpl.generateCacheKey(sleepAction3);
assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3)); assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3));
assertNotNull(executingActionsCache.get(key3)); assertNotNull(executingActionsCache.get(key3));
@@ -531,11 +564,11 @@ public class ActionTrackingServiceImplTest extends TestCase
// Have it finish sleeping, will have been cancelled // Have it finish sleeping, will have been cancelled
sleepActionExec.getExecutingThread().interrupt(); sleepActionExec.getExecutingThread().interrupt();
Thread.sleep(100); Thread.sleep(150);
// TODO Proper cancelled exception and tracking // Ensure the proper cancelled tracking
assertEquals(ActionStatus.Failed, sleepAction3.getExecutionStatus()); assertEquals(ActionStatus.Cancelled, sleepAction3.getExecutionStatus());
assertEquals("Cancelled!", sleepAction3.getExecutionFailureMessage()); assertEquals(null, sleepAction3.getExecutionFailureMessage());
} }

View File

@@ -18,11 +18,23 @@
*/ */
package org.alfresco.service.cmr.action; package org.alfresco.service.cmr.action;
import org.alfresco.repo.action.ActionCancelledException;
import org.alfresco.repo.action.executer.ActionExecuter;
/** /**
* A marker interface that forms part of the Cancel Action contract. * A marker interface that forms part of the Cancel Action contract.
* An action that implements this interface commits to periodically * An action that implements this interface commits to periodically
* asking the {@link ActionService} if a cancel of it has been * asking the {@link ActionTrackingService} if a cancel of it has
* requested, and orderly terminating itself if so. * been requested, and orderly terminating itself if so.
*
* Actions implementing this should, via their
* {@link ActionExecuter}, periodically call
* {@link ActionTrackingService#isCancellationRequested(CancellableAction)}
* to check if a cancel has been requested for them.
* If it has, they should tidy up as much as possible, and then throw
* a {@link ActionCancelledException} to indicate to the
* {@link ActionService} that they ceased running due to a
* cancel.
* *
* @author Nick Burch * @author Nick Burch
*/ */