Author: catholicon
Date: Mon Apr 22 07:46:29 2019
New Revision: 1857937

URL: http://svn.apache.org/viewvc?rev=1857937&view=rev
Log:
OAK-7065: Remove orphan file from local directory in case indexing fails

Test to assert that local directory created due to reindexing get
cleaned up if indexing cycle fails

Added:
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopier.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/CopyOnWriteDirectory.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/DefaultDirectoryFactory.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierCleanupTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneWritesOnSegmentStatsTest.java

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopier.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopier.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopier.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopier.java
 Mon Apr 22 07:46:29 2019
@@ -56,6 +56,7 @@ import org.apache.lucene.store.Directory
 import org.apache.lucene.store.FSDirectory;
 import org.apache.lucene.store.FilterDirectory;
 import org.apache.lucene.store.NoLockFactory;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -124,11 +125,17 @@ public class IndexCopier implements Copy
         return new CopyOnReadDirectory(this, remote, local, prefetchEnabled, 
indexPath, executor);
     }
 
-    public Directory wrapForWrite(LuceneIndexDefinition definition, Directory 
remote, boolean reindexMode, String dirName) throws IOException {
-        Directory local = createLocalDirForIndexWriter(definition, dirName);
+    public Directory wrapForWrite(LuceneIndexDefinition definition, Directory 
remote,
+                                  boolean reindexMode, String dirName,
+                                  COWDirecetoryTracker cowDirecetoryTracker) 
throws IOException {
+        Directory local = createLocalDirForIndexWriter(definition, dirName, 
reindexMode, cowDirecetoryTracker);
         String indexPath = definition.getIndexPath();
         checkIntegrity(indexPath, local, remote);
-        return new CopyOnWriteDirectory(this, remote, local, reindexMode, 
indexPath, executor);
+
+        CopyOnWriteDirectory cowDirectory = new CopyOnWriteDirectory(this, 
remote, local, reindexMode, indexPath, executor);
+        cowDirecetoryTracker.registerOpenedDirectory(cowDirectory);
+
+        return cowDirectory;
     }
 
     @Override
@@ -148,10 +155,16 @@ public class IndexCopier implements Copy
         return indexRootDirectory;
     }
 
-    protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName) throws IOException {
+    protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName,
+                                                     boolean reindexMode,
+                                                     COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
         String indexPath = definition.getIndexPath();
         File indexWriterDir = getIndexDir(definition, indexPath, dirName);
 
+        if (reindexMode) {
+            
cowDirecetoryTracker.registerReindexingLocalDirectory(indexWriterDir);
+        }
+
         //By design indexing in Oak is single threaded so Lucene locking
         //can be disabled
         Directory dir = FSDirectory.open(indexWriterDir, 
NoLockFactory.getNoLockFactory());
@@ -637,4 +650,17 @@ public class IndexCopier implements Copy
             }
         }
     }
+
+    public interface COWDirecetoryTracker {
+        void registerOpenedDirectory(@NotNull CopyOnWriteDirectory directory);
+        void registerReindexingLocalDirectory(@NotNull File dir);
+
+        COWDirecetoryTracker NOOP = new COWDirecetoryTracker() {
+            @Override
+            public void registerOpenedDirectory(CopyOnWriteDirectory 
directory) {}
+
+            @Override
+            public void registerReindexingLocalDirectory(File dir) {}
+        };
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
 Mon Apr 22 07:46:29 2019
@@ -16,11 +16,15 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.plugins.index.*;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier.COWDirecetoryTracker;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory.ActiveDeletedBlobCollector;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory.BlobDeletionCallback;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.CopyOnWriteDirectory;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.DefaultDirectoryFactory;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.DirectoryFactory;
 import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.IndexingQueue;
@@ -50,8 +54,10 @@ import org.jetbrains.annotations.Nullabl
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.LinkedList;
+import java.util.List;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -213,7 +219,12 @@ public class LuceneIndexEditorProvider i
             }
 
             if (writerFactory == null) {
-                writerFactory = new 
DefaultIndexWriterFactory(mountInfoProvider, 
newDirectoryFactory(blobDeletionCallback), writerConfig);
+                COWDirectoryCleanupCallback cowDirectoryCleanupCallback = new 
COWDirectoryCleanupCallback();
+                
indexingContext.registerIndexCommitCallback(cowDirectoryCleanupCallback);
+
+                writerFactory = new 
DefaultIndexWriterFactory(mountInfoProvider,
+                        newDirectoryFactory(blobDeletionCallback, 
cowDirectoryCleanupCallback),
+                        writerConfig);
             }
 
             LuceneIndexEditorContext context = new 
LuceneIndexEditorContext(root, definition, indexDefinition, callback,
@@ -251,8 +262,9 @@ public class LuceneIndexEditorProvider i
         this.inMemoryDocsLimit = inMemoryDocsLimit;
     }
 
-    protected DirectoryFactory newDirectoryFactory(BlobDeletionCallback 
blobDeletionCallback) {
-        return new DefaultDirectoryFactory(indexCopier, blobStore, 
blobDeletionCallback);
+    protected DirectoryFactory newDirectoryFactory(BlobDeletionCallback 
blobDeletionCallback,
+                                                   COWDirecetoryTracker 
cowDirectoryTracker) {
+        return new DefaultDirectoryFactory(indexCopier, blobStore, 
blobDeletionCallback, cowDirectoryTracker);
     }
 
     private LuceneDocumentHolder getDocumentHolder(CommitContext 
commitContext){
@@ -288,4 +300,42 @@ public class LuceneIndexEditorProvider i
     private static CommitContext getCommitContext(IndexingContext 
indexingContext) {
         return (CommitContext) 
indexingContext.getCommitInfo().getInfo().get(CommitContext.NAME);
     }
+
+    private static class COWDirectoryCleanupCallback implements 
IndexCommitCallback, COWDirecetoryTracker {
+        private static final Logger LOG = 
LoggerFactory.getLogger(COWDirectoryCleanupCallback.class);
+
+        private List<CopyOnWriteDirectory> openedCoWDirectories = 
Lists.newArrayList();
+        private List<File> reindexingLocalDirectories = Lists.newArrayList();
+
+
+        @Override
+        public void commitProgress(IndexProgress indexProgress) {
+            // we only worry about failed indexing
+            if (indexProgress == IndexProgress.COMMIT_FAILED) {
+                for (CopyOnWriteDirectory d : openedCoWDirectories) {
+                    try {
+                        d.close();
+                    } catch (Exception e) {
+                        LOG.warn("Error occurred while closing {}", d, e);
+                    }
+                }
+
+                for (File f : reindexingLocalDirectories) {
+                    if ( ! FileUtils.deleteQuietly(f)) {
+                        LOG.warn("Failed to delete {}", f);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void registerOpenedDirectory(@NotNull CopyOnWriteDirectory 
directory) {
+            openedCoWDirectories.add(directory);
+        }
+
+        @Override
+        public void registerReindexingLocalDirectory(@NotNull File dir) {
+            reindexingLocalDirectories.add(dir);
+        }
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/CopyOnWriteDirectory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/CopyOnWriteDirectory.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/CopyOnWriteDirectory.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/CopyOnWriteDirectory.java
 Mon Apr 22 07:46:29 2019
@@ -79,6 +79,7 @@ public class CopyOnWriteDirectory extend
     private final CountDownLatch copyDone = new CountDownLatch(1);
     private final boolean reindexMode;
     private final String indexPath;
+    private boolean closed;
 
     /**
      * Current background task
@@ -203,8 +204,16 @@ public class CopyOnWriteDirectory extend
         return ref.openInput(context);
     }
 
+    public boolean isClosed() {
+        return closed;
+    }
+
     @Override
     public void close() throws IOException {
+        if (isClosed()) {
+            return;
+        }
+
         int pendingCopies = queue.size();
         addTask(STOP);
 
@@ -257,6 +266,8 @@ public class CopyOnWriteDirectory extend
 
         local.close();
         remote.close();
+
+        closed = true;
     }
 
     @Override

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/DefaultDirectoryFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/DefaultDirectoryFactory.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/DefaultDirectoryFactory.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/DefaultDirectoryFactory.java
 Mon Apr 22 07:46:29 2019
@@ -22,7 +22,9 @@ package org.apache.jackrabbit.oak.plugin
 import java.io.File;
 import java.io.IOException;
 
+import org.apache.jackrabbit.oak.plugins.index.IndexCommitCallback;
 import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier.COWDirecetoryTracker;
 import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory.BlobDeletionCallback;
 import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
@@ -41,15 +43,18 @@ public class DefaultDirectoryFactory imp
     private final IndexCopier indexCopier;
     private final GarbageCollectableBlobStore blobStore;
     private final BlobDeletionCallback blobDeletionCallback;
+    private final COWDirecetoryTracker cowDirecetoryTracker;
 
     public DefaultDirectoryFactory(@Nullable IndexCopier indexCopier, 
@Nullable GarbageCollectableBlobStore blobStore) {
-        this(indexCopier, blobStore, BlobDeletionCallback.NOOP);
+        this(indexCopier, blobStore, BlobDeletionCallback.NOOP, 
COWDirecetoryTracker.NOOP);
     }
     public DefaultDirectoryFactory(@Nullable IndexCopier indexCopier, 
@Nullable GarbageCollectableBlobStore blobStore,
-                                   @NotNull 
ActiveDeletedBlobCollectorFactory.BlobDeletionCallback blobDeletionCallback) {
+                                   @NotNull 
ActiveDeletedBlobCollectorFactory.BlobDeletionCallback blobDeletionCallback,
+                                   @NotNull COWDirecetoryTracker 
cowDirectoryTracker) {
         this.indexCopier = indexCopier;
         this.blobStore = blobStore;
         this.blobDeletionCallback = blobDeletionCallback;
+        this.cowDirecetoryTracker = cowDirectoryTracker;
     }
 
     @Override
@@ -65,7 +70,7 @@ public class DefaultDirectoryFactory imp
                 Directory d = indexCopier.wrapForRead(indexPath, definition, 
directory, dirName);
                 d.close();
             }
-            directory = indexCopier.wrapForWrite(definition, directory, 
reindex, dirName);
+            directory = indexCopier.wrapForWrite(definition, directory, 
reindex, dirName, cowDirecetoryTracker);
         }
         return directory;
     }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierCleanupTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierCleanupTest.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierCleanupTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierCleanupTest.java
 Mon Apr 22 07:46:29 2019
@@ -474,7 +474,9 @@ public class IndexCopierCleanupTest {
         }
 
         @Override
-        protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName) throws IOException {
+        protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName,
+                                                         boolean reindexMode,
+                                                         COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
             return new DelayCopyingSimpleFSDirectory(baseFSDir);
         }
 
@@ -487,7 +489,7 @@ public class IndexCopierCleanupTest {
         }
 
         Directory getCoWDir() throws IOException {
-            return wrapForWrite(defn, remote, false, INDEX_DATA_CHILD_NAME);
+            return wrapForWrite(defn, remote, false, INDEX_DATA_CHILD_NAME, 
COWDirecetoryTracker.NOOP);
         }
     }
 

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java
 Mon Apr 22 07:46:29 2019
@@ -595,7 +595,8 @@ public class IndexCopierTest {
         IndexCopier copier = new IndexCopier(sameThreadExecutor(), 
getWorkDir());
 
         LuceneIndexDefinition defn = new LuceneIndexDefinition(root, 
builder.getNodeState(), "/foo");
-        Directory dir = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        Directory dir = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
 
         byte[] t1 = writeFile(dir, "t1");
 
@@ -613,7 +614,8 @@ public class IndexCopierTest {
         IndexCopier copier = new IndexCopier(sameThreadExecutor(), 
getWorkDir());
 
         LuceneIndexDefinition defn = new LuceneIndexDefinition(root, 
builder.getNodeState(), "/foo");
-        Directory dir = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        Directory dir = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
 
         byte[] t1 = writeFile(dir, "t1");
 
@@ -642,7 +644,8 @@ public class IndexCopierTest {
         //State of remote directory should set before wrapping as later
         //additions would not be picked up given COW assume remote directory
         //to be read only
-        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
 
         assertEquals(newHashSet("t1"), newHashSet(local.listAll()));
         assertEquals(t1.length, local.fileLength("t1"));
@@ -699,7 +702,8 @@ public class IndexCopierTest {
         Directory remote = new CloseSafeDir();
         byte[] t1 = writeFile(remote, "t1");
         byte[] t2 = writeFile(remote, "t2");
-        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         assertEquals(newHashSet("t1", "t2"), newHashSet(local.listAll()));
 
         byte[] t3 = writeFile(local, "t3");
@@ -749,7 +753,8 @@ public class IndexCopierTest {
             }
         };
         byte[] t1 = writeFile(remote, "t1");
-        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
 
         //Read should be served from remote
         readRemotes.clear();readLocal.clear();
@@ -779,7 +784,8 @@ public class IndexCopierTest {
 
         Directory remote = new CloseSafeDir();
 
-        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         byte[] t1 = writeFile(local, "t1");
 
         assertTrue(local.fileExists("t1"));
@@ -841,7 +847,8 @@ public class IndexCopierTest {
 
         Directory remote = new CloseSafeDir();
 
-        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         byte[] t1 = writeFile(local, "t1");
 
         assertTrue(local.fileExists("t1"));
@@ -909,7 +916,8 @@ public class IndexCopierTest {
             }
         };
 
-        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         toFail.add("t2");
         byte[] t1 = writeFile(local, "t1");
         byte[] t2 = writeFile(local, "t2");
@@ -947,7 +955,8 @@ public class IndexCopierTest {
             }
         };
 
-        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME);
+        final Directory local = copier.wrapForWrite(defn, remote, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         toPause.add("t2");
         byte[] t1 = writeFile(local, "t1");
         byte[] t2 = writeFile(local, "t2");
@@ -1018,7 +1027,8 @@ public class IndexCopierTest {
         };
 
         //Start copying a file to remote via COW
-        Directory cow1 = copier.wrapForWrite(defn, remote2, false, 
INDEX_DATA_CHILD_NAME);
+        Directory cow1 = copier.wrapForWrite(defn, remote2, false, 
INDEX_DATA_CHILD_NAME,
+                IndexCopier.COWDirecetoryTracker.NOOP);
         byte[] f2 = writeFile(cow1, "f2");
 
         //Before copy is done to remote lets delete f1 from remote and
@@ -1145,7 +1155,9 @@ public class IndexCopierTest {
         }
 
         @Override
-        protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName) throws IOException {
+        protected Directory createLocalDirForIndexWriter(LuceneIndexDefinition 
definition, String dirName,
+                                                         boolean reindexMode,
+                                                         COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
             return baseDir;
         }
     }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 Mon Apr 22 07:46:29 2019
@@ -215,8 +215,9 @@ public class LucenePropertyIndexTest ext
 
                 @Override
                 public Directory wrapForWrite(LuceneIndexDefinition definition,
-                                              Directory remote, boolean 
reindexMode, String dirName) throws IOException {
-                    Directory ret = super.wrapForWrite(definition, remote, 
reindexMode, dirName);
+                                              Directory remote, boolean 
reindexMode, String dirName,
+                                              COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
+                    Directory ret = super.wrapForWrite(definition, remote, 
reindexMode, dirName, cowDirecetoryTracker);
                     cowDir = getFSDirPath(ret);
                     return ret;
                 }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneWritesOnSegmentStatsTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneWritesOnSegmentStatsTest.java?rev=1857937&r1=1857936&r2=1857937&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneWritesOnSegmentStatsTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneWritesOnSegmentStatsTest.java
 Mon Apr 22 07:46:29 2019
@@ -235,8 +235,9 @@ public class LuceneWritesOnSegmentStatsT
 
                 @Override
                 public Directory wrapForWrite(LuceneIndexDefinition definition,
-                                              Directory remote, boolean 
reindexMode, String dirName) throws IOException {
-                    Directory ret = super.wrapForWrite(definition, remote, 
reindexMode, dirName);
+                                              Directory remote, boolean 
reindexMode, String dirName,
+                                              COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
+                    Directory ret = super.wrapForWrite(definition, remote, 
reindexMode, dirName, cowDirecetoryTracker);
                     cowDir = getFSDirPath(ret);
                     return ret;
                 }

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java?rev=1857937&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java
 Mon Apr 22 07:46:29 2019
@@ -0,0 +1,406 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.index.lucene.writer;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closer;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
+import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate;
+import org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
+import 
org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexDefinition;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.CopyOnWriteDirectory;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.*;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.lucene.store.Directory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_DATA_CHILD_NAME;
+import static org.junit.Assert.*;
+
+public class FailedIndexUpdateTest {
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder(new 
File("target"));
+
+    private Closer closer;
+
+    private Root root;
+    private AsyncIndexUpdate asyncIndexUpdate;
+    private LocalDirectoryTrackingIndexCopier copier;
+    private FailOnDemandValidatorProvider failOnDemandValidatorProvider;
+
+    private static final String TEST_CONTENT_PATH = "/test";
+
+    @Before
+    public void setup() throws Exception {
+        closer = Closer.create();
+        createRepository();
+    }
+
+    private void createRepository() throws IOException {
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        closer.register(new ExecutorCloser(executorService));
+        copier = new LocalDirectoryTrackingIndexCopier(executorService, 
temporaryFolder.getRoot());
+        FailIfDefinedEditorProvider luceneEditorProvider = new 
FailIfDefinedEditorProvider(copier);
+
+        IndexEditorProvider editorProvider = new 
CompositeIndexEditorProvider(new NodeCounterEditorProvider(), 
luceneEditorProvider);
+
+        NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
+
+        Oak oak = new Oak(store)
+                .with(new OpenSecurityProvider())
+                ;
+        root = oak.createRoot();
+
+        failOnDemandValidatorProvider = new FailOnDemandValidatorProvider();
+        asyncIndexUpdate = new AsyncIndexUpdate("async", store, 
editorProvider);
+        
asyncIndexUpdate.setValidatorProviders(Collections.singletonList(failOnDemandValidatorProvider));
+    }
+
+    @After
+    public void after() throws IOException {
+        closer.close();
+    }
+
+    @Test
+    public void workingReindexDirCleanUpOnFailureOfOtherIndex() throws 
Exception {
+        createIndex("fails", "foo", true);
+        asyncIndexUpdate.run();
+        assertFalse("Indexing mustn't be failing", 
asyncIndexUpdate.isFailing());
+        copier.clearStats();
+
+        createIndex("reindexing", "foo", false);
+
+        root.getTree("/").addChild("test")
+                .addChild("a").setProperty("foo", "bar");
+        root.commit();
+
+        asyncIndexUpdate.run();
+        assertTrue("Indexing must fail", asyncIndexUpdate.isFailing());
+
+        Set<File> reindexingDirPaths = copier.getReindexingDirPaths();
+        assertEquals(1, reindexingDirPaths.size());
+
+        File reindexingDir = reindexingDirPaths.iterator().next();
+        assertFalse("Reindexing directories must get cleaned up on failure", 
reindexingDir.exists());
+
+        copier.getDirs().forEach((key, value) -> assertTrue("Writer for " + 
key + " must be closed", value.isClosed()));
+    }
+
+    @Test
+    public void workingReindexDirCleanUpOnFailureOfMerge() throws Exception {
+        failOnDemandValidatorProvider.shouldFail = true;
+
+        createIndex("reindexing", "foo", false);
+
+        root.getTree("/").addChild("test")
+                .addChild("a").setProperty("foo", "bar");
+        root.commit();
+
+        asyncIndexUpdate.run();
+        assertTrue("Indexing must fail", asyncIndexUpdate.isFailing());
+
+        Set<File> reindexingDirPaths = copier.getReindexingDirPaths();
+        assertEquals(1, reindexingDirPaths.size());
+
+        File reindexingDir = reindexingDirPaths.iterator().next();
+        assertFalse("Reindexing directories must get cleaned up on failure", 
reindexingDir.exists());
+
+        copier.getDirs().forEach((key, value) -> assertTrue("Writer for " + 
key + " must be closed", value.isClosed()));
+    }
+
+    @Test
+    public void workingIndexDirDoesNotCleanUpOnFailureOfOtherIndex() throws 
Exception {
+        createIndex("fails", "foo", true);
+        createIndex("working", "foo", false);
+        asyncIndexUpdate.run();
+
+        assertFalse("Indexing mustn't be failing", 
asyncIndexUpdate.isFailing());
+
+        copier.clearStats();
+
+        root.getTree("/").addChild("test")
+                .addChild("a").setProperty("foo", "bar");
+        root.commit();
+
+        asyncIndexUpdate.run();
+        assertTrue("Indexing must fail", asyncIndexUpdate.isFailing());
+
+        Set<File> reindexingDirPaths = copier.getReindexingDirPaths();
+        assertEquals("No directories are reindexing", 0, 
reindexingDirPaths.size());
+
+        assertEquals("Number of open directories aren't as expected", 2, 
copier.getDirPaths().size());
+
+        copier.getDirPaths().forEach((key, value) -> assertTrue(key + " must 
not get cleaned up on failure", value.exists()));
+
+        copier.getDirs().forEach((key, value) -> assertTrue("Writer for " + 
key + " must be closed", value.isClosed()));
+    }
+
+    @Test
+    public void workingIndexDirDoesNotCleanUpOnFailureOfMerge() throws 
Exception {
+        createIndex("working", "foo", false);
+        asyncIndexUpdate.run();
+        assertFalse("Indexing mustn't be failing", 
asyncIndexUpdate.isFailing());
+        copier.clearStats();
+
+        failOnDemandValidatorProvider.shouldFail = true;
+
+        root.getTree("/").addChild("test")
+                .addChild("a").setProperty("foo", "bar");
+        root.commit();
+
+        asyncIndexUpdate.run();
+        assertTrue("Indexing must fail", asyncIndexUpdate.isFailing());
+
+
+        Set<File> reindexingDirPaths = copier.getReindexingDirPaths();
+        assertEquals("No directories are reindexing.", 0, 
reindexingDirPaths.size());
+
+        assertEquals("Number of open directories aren't as expected", 1, 
copier.getDirPaths().size());
+
+        copier.getDirPaths().forEach((key, value) -> assertTrue(key + " must 
not get cleaned up on failure", value.exists()));
+
+        copier.getDirs().forEach((key, value) -> assertTrue("Writer for " + 
key + " must be closed", value.isClosed()));
+    }
+
+    private void createIndex(String idxName, String propName, boolean 
shouldFail) throws CommitFailedException {
+        IndexDefinitionBuilder idxBuilder = new IndexDefinitionBuilder();
+
+        idxBuilder
+                .includedPaths(TEST_CONTENT_PATH)
+                .indexRule("nt:base")
+                .property(propName).propertyIndex();
+        Tree idx = 
idxBuilder.build(root.getTree("/oak:index").addChild(idxName));
+        idx.setProperty("shouldFail", shouldFail);
+
+        root.commit();
+    }
+
+    static class LocalDirectoryTrackingIndexCopier extends IndexCopier {
+
+        private final Map<String, CopyOnWriteDirectory> dirs = 
Maps.newHashMap();
+        private final Map<String, File> dirPaths = Maps.newHashMap();
+        private final Set<File> reindexingDirPaths = Sets.newHashSet();
+
+        LocalDirectoryTrackingIndexCopier(Executor executor, File 
indexRootDir) throws IOException {
+            super(executor, indexRootDir);
+        }
+
+        @Override
+        public Directory wrapForWrite(LuceneIndexDefinition definition,
+                                                        Directory remote,
+                                                        boolean reindexMode, 
String dirName,
+                                                        COWDirecetoryTracker 
cowDirecetoryTracker) throws IOException {
+            CopyOnWriteDirectory dir = (CopyOnWriteDirectory)
+                    super.wrapForWrite(definition, remote, reindexMode, 
dirName, cowDirecetoryTracker);
+
+            String indexPath = definition.getIndexPath();
+            dirs.put(indexPath, dir);
+            File dirPath = getIndexDir(definition, indexPath, dirName);
+            dirPaths.put(indexPath, dirPath);
+            if (reindexMode) {
+                reindexingDirPaths.add(dirPath);
+            }
+
+            return dir;
+        }
+
+        void clearStats() {
+            dirs.clear();
+            dirPaths.clear();
+            reindexingDirPaths.clear();
+        }
+
+        Map<String, CopyOnWriteDirectory> getDirs() {
+            return dirs;
+        }
+
+        Map<String, File> getDirPaths() {
+            return dirPaths;
+        }
+
+        Set<File> getReindexingDirPaths() {
+            return reindexingDirPaths;
+        }
+    }
+
+    private static class FailIfDefinedEditorProvider extends 
LuceneIndexEditorProvider {
+        FailIfDefinedEditorProvider(IndexCopier copier) {
+            super(copier);
+        }
+
+        @Override
+        public Editor getIndexEditor(@NotNull String type, @NotNull 
NodeBuilder definition,
+                                     @NotNull NodeState root,
+                                     @NotNull IndexUpdateCallback callback) 
throws CommitFailedException {
+            Editor editor = super.getIndexEditor(type, definition, root, 
callback);
+            if (definition.getBoolean("shouldFail")) {
+                editor = new FailOnLeavePathEditor(editor, TEST_CONTENT_PATH);
+            }
+            return editor;
+        }
+    }
+
+    private static class FailOnLeavePathEditor implements Editor {
+        private final Editor delegate;
+        private final String failingPath;
+        final String currPath;
+
+        FailOnLeavePathEditor(Editor delegate, String failingPath) {
+            this(delegate, failingPath, "", "");
+        }
+
+        private FailOnLeavePathEditor(Editor delegate, String failingPath, 
String parentPath, String name) {
+            this.delegate = delegate != null ? delegate : new DefaultEditor();
+            this.failingPath = failingPath;
+            this.currPath = ("/".equals(parentPath) ? parentPath : parentPath 
+ "/") + name;
+        }
+
+        @Override
+        public void enter(NodeState before, NodeState after) throws 
CommitFailedException {
+            delegate.enter(before, after);
+        }
+
+        @Override
+        public void leave(NodeState before, NodeState after) throws 
CommitFailedException {
+            delegate.leave(before, after); // delegate call before failing
+
+            if (failingPath.equals(currPath)) {
+                throw new CommitFailedException("index-fail", 1, null);
+            }
+        }
+
+        @Override
+        public void propertyAdded(PropertyState after) throws 
CommitFailedException {
+            delegate.propertyAdded(after);
+        }
+
+        @Override
+        public void propertyChanged(PropertyState before, PropertyState after) 
throws CommitFailedException {
+            delegate.propertyChanged(before, after);
+        }
+
+        @Override
+        public void propertyDeleted(PropertyState before) throws 
CommitFailedException {
+            delegate.propertyDeleted(before);
+        }
+
+        @Override
+        @Nullable
+        public Editor childNodeAdded(String name, NodeState after) throws 
CommitFailedException {
+            return new FailOnLeavePathEditor(delegate.childNodeAdded(name, 
after), failingPath, currPath, name);
+        }
+
+        @Override
+        @Nullable
+        public Editor childNodeChanged(String name, NodeState before, 
NodeState after) throws CommitFailedException {
+            return new FailOnLeavePathEditor(delegate.childNodeChanged(name, 
before, after), failingPath, currPath, name);
+        }
+
+        @Override
+        @Nullable
+        public Editor childNodeDeleted(String name, NodeState before) throws 
CommitFailedException {
+            return new FailOnLeavePathEditor(delegate.childNodeDeleted(name, 
before), failingPath, currPath, name);
+        }
+    }
+
+    static class FailOnDemandValidatorProvider extends ValidatorProvider {
+
+        boolean shouldFail;
+        static final String FAILING_PATH_FRAGMENT = INDEX_DATA_CHILD_NAME;
+
+        @Override
+        protected @Nullable Validator getRootValidator(NodeState before, 
NodeState after, CommitInfo info) {
+            return new FailOnDemandValidator(new DefaultValidator());
+        }
+
+        class FailOnDemandValidator extends FailOnLeavePathEditor implements 
Validator {
+            final Validator delegate;
+
+            FailOnDemandValidator(Validator delegate) {
+                super(delegate, "");
+                this.delegate = delegate;
+            }
+
+            private FailOnDemandValidator(Validator delegate, String 
parentPath, String name) {
+                super(delegate, "", parentPath, name);
+                this.delegate = delegate != null ? delegate : new 
DefaultValidator();
+            }
+
+            @Override
+            public void leave(NodeState before, NodeState after) throws 
CommitFailedException {
+                super.leave(before, after);
+
+                if (shouldFail && currPath.contains(FAILING_PATH_FRAGMENT)) {
+                    throw new CommitFailedException("validator-fail", 1, null);
+                }
+            }
+
+            @Override
+            @Nullable
+            public Validator childNodeAdded(String name, NodeState after) 
throws CommitFailedException {
+                return new FailOnDemandValidator(delegate.childNodeAdded(name, 
after), currPath, name);
+            }
+
+            @Override
+            @Nullable
+            public Validator childNodeChanged(String name, NodeState before, 
NodeState after) throws CommitFailedException {
+                return new 
FailOnDemandValidator(delegate.childNodeChanged(name, before, after), currPath, 
name);
+            }
+
+            @Override
+            @Nullable
+            public Validator childNodeDeleted(String name, NodeState before) 
throws CommitFailedException {
+                return new 
FailOnDemandValidator(delegate.childNodeDeleted(name, before), currPath, name);
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/writer/FailedIndexUpdateTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to