From 17bcf56a932b667252ffa410966df4545a98d7a1 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 21 Jul 2010 15:49:19 +0000 Subject: [PATCH] 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 --- .../repo/action/ActionCancelledException.java | 35 ++++++++++++ .../org/alfresco/repo/action/ActionImpl.java | 31 +++++++++++ .../repo/action/ActionServiceImplTest.java | 5 +- .../action/ActionTrackingServiceImpl.java | 36 ++++++++++--- .../action/ActionTrackingServiceImplTest.java | 53 +++++++++++++++---- .../service/cmr/action/CancellableAction.java | 16 +++++- 6 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 source/java/org/alfresco/repo/action/ActionCancelledException.java diff --git a/source/java/org/alfresco/repo/action/ActionCancelledException.java b/source/java/org/alfresco/repo/action/ActionCancelledException.java new file mode 100644 index 0000000000..0f2964ba1c --- /dev/null +++ b/source/java/org/alfresco/repo/action/ActionCancelledException.java @@ -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 . + */ +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()); + } +} diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index 1ba9ee62d9..5f8bcd953a 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -106,6 +106,16 @@ public class ActionImpl extends ParameterizedItemImpl implements Action */ private List actionConditions = new ArrayList(); + /** + * 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, * or null if it hasn't yet. @@ -466,6 +476,27 @@ public class ActionImpl extends ParameterizedItemImpl implements Action 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() { return executionStartDate; } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index aaf13f378c..10b60078d1 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -795,6 +795,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action"); assertNotNull(sleepAction); + sleepAction.setSleepMs(10); final int actionSubmissonCount = 4; // Rather arbitrary count. for (int i = 0; i < actionSubmissonCount; i ++) @@ -883,7 +884,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest postAsyncActionTest( this.transactionService, - 1000, + 1000l, 10, new AsyncTest() { @@ -1344,7 +1345,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest boolean cancelled = actionTrackingService.isCancellationRequested(ca); if(cancelled) { - throw new RuntimeException("Cancelled!"); + throw new ActionCancelledException(ca); } } } diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java index 88ae8dab97..f9a7b01b93 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java @@ -96,7 +96,12 @@ public class ActionTrackingServiceImpl implements ActionTrackingService { this.executingActionsCache = executingActionsCache; } - + + + /** Used by unit tests only */ + protected void resetNextExecutionId() { + this.nextExecutionId = 1; + } public void recordActionPending(Action action) @@ -139,6 +144,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService // TODO assign it a (unique) execution ID // (Keep checking to see if the key is used as we // increase nextExecutionId until it isn't) + ((ActionImpl)action).setExecutionInstance(nextExecutionId++); // TODO String key = generateCacheKey(action); // Put it into the cache @@ -154,12 +160,30 @@ public class ActionTrackingServiceImpl implements ActionTrackingService { if (logger.isDebugEnabled() == true) { - logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage()); + 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()); + } } + // Record when it finished ((ActionImpl)action).setExecutionEndDate(new Date()); - ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed); - ((ActionImpl)action).setExecutionFailureMessage(exception.getMessage()); + + // 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).setExecutionFailureMessage(exception.getMessage()); + } // Remove it from the cache, as it's no longer running String key = generateCacheKey(action); @@ -334,7 +358,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService return action.getActionDefinitionName() + cacheKeyPartSeparator + action.getId() + cacheKeyPartSeparator + - "1"//action.getExecutionInstance // TODO + ((ActionImpl)action).getExecutionInstance() ; } protected static String generateCacheKey(ExecutionSummary summary) @@ -381,7 +405,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService return new ExecutionSummary( action.getActionDefinitionName(), action.getId(), - 1 // TODO + ((ActionImpl)action).getExecutionInstance() ); } } diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index 7ee869eaf8..a1c4e634cf 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -113,6 +113,10 @@ public class ActionTrackingServiceImplTest extends TestCase 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 SleepActionExecuter.registerIfNeeded(ctx); } @@ -120,10 +124,13 @@ public class ActionTrackingServiceImplTest extends TestCase /** Creating cache keys */ public void testCreateCacheKeys() throws Exception { - Action action = createWorkingSleepAction("1234"); + ActionImpl action = (ActionImpl)createWorkingSleepAction("1234"); assertEquals("sleep-action", action.getActionDefinitionName()); assertEquals("1234", action.getId()); - // assertNull(action.getExecutionInstance()); // TODO + assertEquals(-1, action.getExecutionInstance()); + + // Give it a predictable execution instance + action.setExecutionInstance(1); // From an action String key = ActionTrackingServiceImpl.generateCacheKey(action); @@ -138,8 +145,11 @@ public class ActionTrackingServiceImplTest extends TestCase /** Creating ExecutionDetails and ExecutionSummary */ public void testExecutionDetailsSummary() throws Exception { - // Create the ExecutionSummary from an action + // Create an action with a known execution instance Action action = createWorkingSleepAction("1234"); + ((ActionImpl)action).setExecutionInstance(1); + + // Create the ExecutionSummary from an action String key = ActionTrackingServiceImpl.generateCacheKey(action); ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); @@ -161,10 +171,15 @@ public class ActionTrackingServiceImplTest extends TestCase assertEquals(1, d.getExecutionInstance()); assertEquals(null, d.getPersistedActionRef()); assertEquals(null, d.getStartedAt()); + + // TODO Check machine details } - // Running an action gives it an execution ID - // TODO + /** Running an action gives it an execution ID */ + public void testExecutionInstanceAssignment() + { + // TODO + } /** * The correct things happen with the cache @@ -181,10 +196,12 @@ public class ActionTrackingServiceImplTest extends TestCase // Can complete or fail, won't be there actionTrackingService.recordActionComplete(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Completed, action.getExecutionStatus()); assertEquals(null, executingActionsCache.get(key)); actionTrackingService.recordActionFailure(action, new Exception("Testing")); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Failed, action.getExecutionStatus()); assertEquals("Testing", action.getExecutionFailureMessage()); assertEquals(null, executingActionsCache.get(key)); @@ -192,12 +209,14 @@ public class ActionTrackingServiceImplTest extends TestCase // Pending won't add it in either actionTrackingService.recordActionPending(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Pending, action.getExecutionStatus()); assertEquals(null, executingActionsCache.get(key)); - // Run it, will go in + // Run it, will go into the cache actionTrackingService.recordActionExecuting(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Running, action.getExecutionStatus()); assertNotNull(null, executingActionsCache.get(key)); @@ -213,14 +232,17 @@ public class ActionTrackingServiceImplTest extends TestCase // Completion removes it actionTrackingService.recordActionComplete(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Completed, action.getExecutionStatus()); assertEquals(null, executingActionsCache.get(key)); // Failure removes it actionTrackingService.recordActionExecuting(action); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertNotNull(null, executingActionsCache.get(key)); actionTrackingService.recordActionFailure(action, new Exception("Testing")); + key = ActionTrackingServiceImpl.generateCacheKey(action); assertEquals(ActionStatus.Failed, action.getExecutionStatus()); assertEquals("Testing", action.getExecutionFailureMessage()); assertEquals(null, executingActionsCache.get(key)); @@ -252,6 +274,10 @@ public class ActionTrackingServiceImplTest extends TestCase // to be started txn.commit(); Thread.sleep(150); + + + // Will get an execution instance id, so a new key + key = ActionTrackingServiceImpl.generateCacheKey(action); // Check it's in the cache @@ -309,6 +335,10 @@ public class ActionTrackingServiceImplTest extends TestCase Thread.sleep(150); + // Will get an execution instance id, so a new key + key = ActionTrackingServiceImpl.generateCacheKey(action); + + // Check it's in the cache System.out.println("Checking the cache for " + key); assertNotNull(executingActionsCache.get(key)); @@ -521,6 +551,9 @@ public class ActionTrackingServiceImplTest extends TestCase txn.commit(); Thread.sleep(150); + // Get the updated key, and check + key3 = ActionTrackingServiceImpl.generateCacheKey(sleepAction3); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3)); assertNotNull(executingActionsCache.get(key3)); @@ -531,11 +564,11 @@ public class ActionTrackingServiceImplTest extends TestCase // Have it finish sleeping, will have been cancelled sleepActionExec.getExecutingThread().interrupt(); - Thread.sleep(100); + Thread.sleep(150); - // TODO Proper cancelled exception and tracking - assertEquals(ActionStatus.Failed, sleepAction3.getExecutionStatus()); - assertEquals("Cancelled!", sleepAction3.getExecutionFailureMessage()); + // Ensure the proper cancelled tracking + assertEquals(ActionStatus.Cancelled, sleepAction3.getExecutionStatus()); + assertEquals(null, sleepAction3.getExecutionFailureMessage()); } diff --git a/source/java/org/alfresco/service/cmr/action/CancellableAction.java b/source/java/org/alfresco/service/cmr/action/CancellableAction.java index 9890bf5a1e..95314c23e0 100644 --- a/source/java/org/alfresco/service/cmr/action/CancellableAction.java +++ b/source/java/org/alfresco/service/cmr/action/CancellableAction.java @@ -18,11 +18,23 @@ */ 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. * An action that implements this interface commits to periodically - * asking the {@link ActionService} if a cancel of it has been - * requested, and orderly terminating itself if so. + * asking the {@link ActionTrackingService} if a cancel of it has + * 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 */