From abd3c793dfa7890374b0e6ac9b5861c11bfd76e0 Mon Sep 17 00:00:00 2001
From: jakubs <jakubs>
Date: Fri, 11 May 2012 15:39:44 +0000
Subject: [PATCH] SP-45, BIS-21 Autorecovery - rollback stack modifications

SVN: 25244
---
 ...tOmniscientTopLevelDataSetRegistrator.java | 18 ++++++-
 .../DataSetRegistrationService.java           |  2 +-
 .../DataSetStorageRecoveryManager.java        |  3 ++
 .../etlserver/registrator/IRollbackStack.java | 12 +++++
 .../api/v1/impl/AbstractTransactionState.java | 31 +++++++++--
 .../impl/DataSetRegistrationTransaction.java  | 53 +++++++++++++------
 .../api/v1/impl/RollbackStack.java            | 53 ++++++++++++++++++-
 .../v2/JythonTopLevelDataSetHandlerV2.java    |  1 -
 .../api/v1/impl/RollbackStackTest.java        | 42 +++++++++++++++
 9 files changed, 190 insertions(+), 25 deletions(-)

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java
index 93eb48b58e3..20972db6e79 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java
@@ -58,7 +58,9 @@ import ch.systemsx.cisd.etlserver.registrator.DataSetStorageAlgorithmRunner.IPre
 import ch.systemsx.cisd.etlserver.registrator.DataSetStorageAlgorithmRunner.IRollbackDelegate;
 import ch.systemsx.cisd.etlserver.registrator.IDataSetOnErrorActionDecision.ErrorType;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.AbstractTransactionState;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSetRegistrationTransaction;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.RollbackStack.IRollbackStackDelegate;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationError;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationScriptRunner;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
@@ -278,8 +280,10 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat
                         new ReentrantLock(), FileOperations.getMonitoredInstanceForCurrentThread(),
                         onErrorDecision);
 
+        state.fileOperations.mkdirs(getRollBackStackParentFolder());
+        
         DataSetRegistrationTransaction
-                .rollbackDeadTransactions(globalState.getDssInternalTempDir());
+                .rollbackDeadTransactions(getRollBackStackParentFolder());
 
     }
 
@@ -293,6 +297,11 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat
         return state.registrationLock;
     }
 
+    public File getRollBackStackParentFolder()
+    {
+        return getGlobalState().getDssInternalTempDir();
+    }
+    
     /**
      * returns the recovery marker file if found, or null otherwise. It first checks if the incoming
      * is the marker file, then if there is a marker file corresponding to this incoming file
@@ -527,6 +536,8 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat
 
         DssRegistrationLogger logger = recoveryState.getRegistrationLogger(state);
 
+        logger.log("The registration has been disturbed. Will try to recover...");
+        
         // rollback delegate
         final List<Throwable> encounteredErrors = new ArrayList<Throwable>();
 
@@ -578,7 +589,10 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat
             if (registeredDataSets.isEmpty())
             {
                 operationLog.info("Recovery hasn't found registration artifacts in the application server. Registration of metadata was not succesfull.");
-                // recoveryState.getRollbackStack().rollbackAll(Live transaction state);./fe
+                
+                IRollbackStackDelegate rollbackStackDelegate = new AbstractTransactionState.LiveTransactionRollbackDelegate(state.getGlobalState().getStagingDir());
+                
+                recoveryState.getRollbackStack().rollbackAll(rollbackStackDelegate);
                 // encounteredErrors.add(ex);
                 //
                 // UnstoreDataAction action =
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java
index 74e7cdf9ac2..177a2be39a3 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java
@@ -152,7 +152,7 @@ public class DataSetRegistrationService<T extends DataSetInformation> implements
 
         // Clone this service for the transaction to keep them independent
         DataSetRegistrationTransaction<T> transaction =
-                createTransaction(registrator.getGlobalState().getDssInternalTempDir(),
+                createTransaction(registrator.getRollBackStackParentFolder(),
                         workingDirectory, stagingDirectory, detailsFactory);
 
         transactions.add(transaction);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageRecoveryManager.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageRecoveryManager.java
index 245dd7dd035..128c2843092 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageRecoveryManager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageRecoveryManager.java
@@ -41,6 +41,8 @@ public class DataSetStorageRecoveryManager implements IDataSetStorageRecoveryMan
                 new DataSetStoragePrecommitRecoveryState<T>(runner.getDataSetStorageAlgorithms(),
                         runner.getDssRegistrationLogger(), runner.getRollbackStack(), incoming);
 
+        runner.getRollbackStack().setLockedState(true);
+        
         FileUtilities.writeToFile(serializedFile, recoveryState);
 
         File processingMarkerFile = getProcessingMarkerFile(runner);
@@ -83,6 +85,7 @@ public class DataSetStorageRecoveryManager implements IDataSetStorageRecoveryMan
     public <T extends DataSetInformation> void registrationCompleted(
             DataSetStorageAlgorithmRunner<T> runner)
     {
+        runner.getRollbackStack().setLockedState(false);
         // Cleanup the state we have accumulated
         File markerFile = getProcessingMarkerFile(runner);
         FileUtilities.delete(markerFile);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IRollbackStack.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IRollbackStack.java
index 712de5d26ef..819f12e70b7 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IRollbackStack.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IRollbackStack.java
@@ -29,4 +29,16 @@ public interface IRollbackStack
      * Push the command onto the stack and execute it.
      */
     void pushAndExecuteCommand(ITransactionalCommand cmd);
+    
+    /**
+     * Sets the locked state of this rollback stack. Changing this state to true results in creating
+     * or deleting the marker file.
+     */
+    public void setLockedState(boolean lockedState);
+    
+    /**
+     * Returns whether this rollback stack is in locked state (i.e. it cannot execute any rollback
+     * actions)
+     */
+    public boolean isLockedState();
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
index 2942d2ee1ee..89354b27b33 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java
@@ -45,6 +45,7 @@ import ch.systemsx.cisd.etlserver.registrator.api.v1.IProject;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.ISpace;
 import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure;
+import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.RollbackStack.IRollbackStackDelegate;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IDataSetImmutable;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IExperimentImmutable;
@@ -70,7 +71,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFa
  * 
  * @author Chandrasekhar Ramakrishnan
  */
-abstract class AbstractTransactionState<T extends DataSetInformation>
+public abstract class AbstractTransactionState<T extends DataSetInformation>
 {
     protected final DataSetRegistrationTransaction<T> parent;
 
@@ -100,8 +101,9 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
      * @author Chandrasekhar Ramakrishnan
      */
     static class LiveTransactionState<T extends DataSetInformation> extends
-            AbstractTransactionState<T> implements RollbackStack.IRollbackStackDelegate
+            AbstractTransactionState<T>
     {
+
         // Default to polling every 10 seconds and waiting for up to 5 minutes
         private static int fileSystemAvailablityWaitCount = 6 * 5;
 
@@ -692,7 +694,7 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
          */
         public void rollback()
         {
-            rollbackStack.rollbackAll(this);
+            rollbackStack.rollbackAll(new LiveTransactionRollbackDelegate(stagingDirectory));
             registeredDataSets.clear();
             for (DynamicTransactionQuery query : queriesToCommit.values())
             {
@@ -847,6 +849,29 @@ abstract class AbstractTransactionState<T extends DataSetInformation>
         {
             return false;
         }
+    }
+
+    /**
+     * Rollback stack delegate that checks whether the given filesystem is accessible before letting
+     * the rollback continue.
+     */
+    public static class LiveTransactionRollbackDelegate implements IRollbackStackDelegate
+    {
+        private final File stagingDirectory;
+
+        private final int fileSystemAvailablityWaitCount;
+
+        private final int fileSystemAvailablityPollingWaitTimeMs;
+
+        /**
+         * @param stagingDirectory Expects a staging directory
+         */
+        public LiveTransactionRollbackDelegate(File stagingDirectory)
+        {
+            this.stagingDirectory = stagingDirectory;
+            this.fileSystemAvailablityWaitCount = LiveTransactionState.fileSystemAvailablityWaitCount;
+            this.fileSystemAvailablityPollingWaitTimeMs = LiveTransactionState.fileSystemAvailablityPollingWaitTimeMs;
+        }
 
         public void willContinueRollbackAll(RollbackStack stack)
         {
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
index 2d74f6fa30e..c2240a884df 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java
@@ -98,11 +98,7 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem
     static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             DataSetRegistrationTransaction.class);
 
-    /**
-     * Check if there are any uncompleted transactions and roll them back. To be called during
-     * startup of a thread.
-     */
-    public static synchronized void rollbackDeadTransactions(File rollBackStackParentFolder)
+    public static synchronized RollbackStack[] findRollbackStacks(File rollBackStackParentFolder)
     {
         File[] rollbackQueue1Files = rollBackStackParentFolder.listFiles(new FilenameFilter()
             {
@@ -110,25 +106,50 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem
                 {
                     return name.endsWith(ROLLBACK_QUEUE1_FILE_NAME_SUFFIX);
                 }
-
             });
 
-        for (File rollbackStackQueue1 : rollbackQueue1Files)
+        RollbackStack[] rollbackStacks = new RollbackStack[rollbackQueue1Files.length];
+
+        for (int i = 0; i < rollbackQueue1Files.length; i++)
         {
+            File rollbackStackQueue1 = rollbackQueue1Files[i];
             RollbackStack stack = createExistingRollbackStack(rollbackStackQueue1);
-            operationLog.info("Found dead rollback stack: " + rollbackStackQueue1
-                    + ". Rolling back.");
+            rollbackStacks[i] = stack;
+        }
+        return rollbackStacks;
+    }
+
+    /**
+     * Check if there are any uncompleted transactions and roll them back. To be called during
+     * startup of a thread.
+     */
+    public static synchronized void rollbackDeadTransactions(File rollBackStackParentFolder)
+    {
 
-            try
+        RollbackStack[] stacks = findRollbackStacks(rollBackStackParentFolder);
+        for (RollbackStack stack : stacks)
+        {
+            if (stack.isLockedState())
             {
-                stack.rollbackAll();
-            } catch (Throwable ex)
+                operationLog.info("Found rollback stack in locked state: "
+                        + stack.getBackingFiles()[0] + ". Not Rolling back.");
+            } else
             {
-                // This should ever happen since rollbackAll should handle execptions, but is here
-                // as a safeguard.
-                operationLog.error("Encountered error rolling back transaction:", ex);
+                operationLog.info("Found dead rollback stack: " + stack.getBackingFiles()[0]
+                        + ". Rolling back.");
+
+                try
+                {
+                    stack.rollbackAll();
+                } catch (Throwable ex)
+                {
+                    // This should ever happen since rollbackAll should handle execptions, but is
+                    // here
+                    // as a safeguard.
+                    operationLog.error("Encountered error rolling back transaction:", ex);
+                }
+                stack.discard();
             }
-            stack.discard();
         }
     }
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStack.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStack.java
index 6a8d8662dee..e633626329a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStack.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStack.java
@@ -17,8 +17,8 @@
 package ch.systemsx.cisd.etlserver.registrator.api.v1.impl;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.Serializable;
-import java.util.Arrays;
 import java.util.Queue;
 
 import org.apache.commons.lang.builder.ToStringBuilder;
@@ -61,6 +61,8 @@ public class RollbackStack implements IRollbackStack
 
     private final File queue2File;
 
+    private final File lockedMarkerFile;
+
     // These are not final because they get swapped around.
     private PersistentExtendedBlockingQueueDecorator<StackElement> liveLifo;
 
@@ -78,6 +80,9 @@ public class RollbackStack implements IRollbackStack
         this.queue1File = queue1File;
         this.queue2File = queue2File;
 
+        this.lockedMarkerFile =
+                new File(queue1File.getParentFile(), queue1File.getName() + ".LOCKED");
+
         PersistentExtendedBlockingQueueDecorator<StackElement> queue1 =
                 ExtendedBlockingQueueFactory.createSmartQueue(queue1File, false);
         PersistentExtendedBlockingQueueDecorator<StackElement> queue2 =
@@ -152,7 +157,7 @@ public class RollbackStack implements IRollbackStack
     /**
      * Rollback the top of the stack and pop it from the stack
      */
-    public ITransactionalCommand rollbackAndPop()
+    ITransactionalCommand rollbackAndPop()
     {
         StackElement elt = peek();
         try
@@ -240,6 +245,11 @@ public class RollbackStack implements IRollbackStack
      */
     public void rollbackAll(IRollbackStackDelegate delegate)
     {
+        if (isLockedState())
+        {
+            throw new IllegalStateException("Rollback stack is in the locked state. Triggering rollback forbidden.");
+        }
+        
         getOperationLog().info("Rolling back stack " + this);
         // Pop and rollback all
         while (size() > 0)
@@ -254,6 +264,10 @@ public class RollbackStack implements IRollbackStack
      */
     public void discard()
     {
+        if (isLockedState()) 
+        {
+            throw new IllegalStateException("Discarding of locked rollback stack is illegal. Set locked to false first.");
+        }
         // Close the persistent queues
         liveLifo.close();
         tempLifo.close();
@@ -264,6 +278,7 @@ public class RollbackStack implements IRollbackStack
         // Delete the files
         queue1File.delete();
         queue2File.delete();
+
     }
 
     /**
@@ -276,6 +291,40 @@ public class RollbackStack implements IRollbackStack
             { queue1File, queue2File };
     }
 
+    public void setLockedState(boolean lockedState)
+    {
+        if (!lockedState && isLockedState())
+        {
+            deleteLockedMarkerFile();
+        } else if (lockedState && false == isLockedState())
+        {
+            createLockedMarkerFile();
+        }
+    }
+
+    public boolean isLockedState()
+    {
+        return lockedMarkerFile.exists();
+    }
+
+    private void deleteLockedMarkerFile()
+    {
+       lockedMarkerFile.delete();
+    }
+
+    private void createLockedMarkerFile()
+    {
+        try
+        {
+            lockedMarkerFile.createNewFile();
+        } catch (IOException ex)
+        {
+            getOperationLog().fatal(
+                    "Failed to create rollback stack lock marker file "
+                            + lockedMarkerFile.getAbsolutePath());
+        }
+    }
+
     /**
      * A stack element combines the command with an order. The order is used to implement the
      * ordering in the queue.
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/JythonTopLevelDataSetHandlerV2.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/JythonTopLevelDataSetHandlerV2.java
index b67dd32475a..cc818aca303 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/JythonTopLevelDataSetHandlerV2.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/JythonTopLevelDataSetHandlerV2.java
@@ -96,5 +96,4 @@ public class JythonTopLevelDataSetHandlerV2<T extends DataSetInformation> extend
     {
         return false;
     }
-
 }
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStackTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStackTest.java
index 386d33fed6d..cb5692c0a9f 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStackTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/RollbackStackTest.java
@@ -48,6 +48,48 @@ public class RollbackStackTest extends AbstractTestWithRollbackStack
         assertTrue(cmd2.rolledbackBeforePredecessor);
         assertEquals(cmd2.predecessor, cmd1);
     }
+    
+    @Test
+    public void testCantRollbackIfLocked()
+    {
+        // Create some commands
+        TrackingCommand cmd1 = new TrackingCommand();
+        TrackingCommand cmd2 = new TrackingCommand(cmd1);
+
+        // Add them to the stack
+        rollbackStack.pushAndExecuteCommand(cmd1);
+        rollbackStack.pushAndExecuteCommand(cmd2);
+        assertEquals(TrackingCommandStatus.EXECUTED, cmd1.status);
+        assertEquals(TrackingCommandStatus.EXECUTED, cmd2.status);
+        
+        assertEquals(
+                "RollbackStack[{StackElement [command=TrackingCommand [status=EXECUTED], order=0],StackElement [command=TrackingCommand [status=EXECUTED], order=1]}]",
+                rollbackStack.toString());
+
+        rollbackStack.setLockedState(true);
+        
+        try {
+            rollbackStack.rollbackAll();
+            fail("The rollbackAll should fail when the rollback stack is in locked state");
+        } catch (Exception ex)
+        {
+            //should catch the exception, because the rollback stack is in the locked state
+        }
+        
+        assertEquals(TrackingCommandStatus.EXECUTED, cmd1.status);
+        assertEquals(TrackingCommandStatus.EXECUTED, cmd2.status);
+        
+        rollbackStack.setLockedState(false);
+        
+        // Rollback and check that the rollback occurred correctly
+        rollbackStack.rollbackAll();
+        
+        assertEquals(TrackingCommandStatus.ROLLEDBACK, cmd1.status);
+        assertEquals(TrackingCommandStatus.ROLLEDBACK, cmd2.status);
+        assertTrue(cmd2.rolledbackBeforePredecessor);
+        assertEquals(cmd2.predecessor, cmd1);
+    }
+    
 
     @Test
     public void testResume()
-- 
GitLab