Author: chetanm
Date: Thu Mar 12 15:22:46 2015
New Revision: 1666220

URL: http://svn.apache.org/r1666220
Log:
OAK-2557 - VersionGC uses way too much memory if there is a large pile of 
garbage

Added:
    
jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java
   (with props)
    
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java
   (with props)
Modified:
    jackrabbit/oak/trunk/oak-commons/pom.xml
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCWithSplitTest.java

Modified: jackrabbit/oak/trunk/oak-commons/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/pom.xml?rev=1666220&r1=1666219&r2=1666220&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-commons/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-commons/pom.xml Thu Mar 12 15:22:46 2015
@@ -93,6 +93,11 @@
       <artifactId>oak-mk-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.4</version>
+    </dependency>
 
     <!-- Test dependencies -->
     <dependency>

Added: 
jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java?rev=1666220&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java
 Thu Mar 12 15:22:46 2015
@@ -0,0 +1,255 @@
+/*
+ * 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.commons.sort;
+
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.LineIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class to store a list of string and perform sort on that. For small 
size
+ * the list would be maintained in memory. If the size crosses the required 
threshold then
+ * the sorting would be performed externally
+ */
+public class StringSort implements Closeable {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    public static final int BATCH_SIZE = 2048;
+
+    private final int overflowToDiskThreshold;
+    private final Comparator<String> comparator;
+
+    private final List<String> ids = Lists.newArrayList();
+    private long size;
+
+    private final List<String> inMemBatch = Lists.newArrayList();
+
+    private boolean useFile;
+    private PersistentState persistentState;
+
+    public StringSort(int overflowToDiskThreshold, Comparator<String> 
comparator) {
+        this.overflowToDiskThreshold = overflowToDiskThreshold;
+        this.comparator = comparator;
+    }
+
+    public void add(String id) throws IOException {
+        if (useFile) {
+            addToBatch(id);
+        } else {
+            ids.add(id);
+            if (ids.size() >= overflowToDiskThreshold) {
+                flushToFile(ids);
+                useFile = true;
+                log.debug("In memory buffer crossed the threshold of {}. " +
+                        "Switching to filesystem [{}] to manage the state", 
overflowToDiskThreshold, persistentState);
+            }
+        }
+        size++;
+    }
+
+    public void sort() throws IOException {
+        if (useFile) {
+            //Flush the last batch
+            flushToFile(inMemBatch);
+            persistentState.sort();
+        } else {
+            Collections.sort(ids, comparator);
+        }
+    }
+
+    public Iterator<String> getIds() throws IOException {
+        if (useFile) {
+            return persistentState.getIterator();
+        } else {
+            return ids.iterator();
+        }
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    public boolean usingFile() {
+        return useFile;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (persistentState != null) {
+            persistentState.close();
+        }
+    }
+
+    private void addToBatch(String id) throws IOException {
+        inMemBatch.add(id);
+        if (inMemBatch.size() >= BATCH_SIZE) {
+            flushToFile(inMemBatch);
+        }
+    }
+
+    private void flushToFile(List<String> ids) throws IOException {
+        BufferedWriter w = getPersistentState().getWriter();
+        for (String id : ids) {
+            w.write(id);
+            w.newLine();
+        }
+        ids.clear();
+    }
+
+    private PersistentState getPersistentState() {
+        //Lazily initialize the persistent state
+        if (persistentState == null) {
+            persistentState = new PersistentState(comparator);
+        }
+        return persistentState;
+    }
+
+    private static class PersistentState implements Closeable {
+        /**
+         * Maximum loop count when creating temp directories.
+         */
+        private static final int TEMP_DIR_ATTEMPTS = 10000;
+
+        private final Charset charset = Charsets.UTF_8;
+        private final File workDir;
+        private final Comparator<String> comparator;
+        private File idFile;
+        private File sortedFile;
+        private BufferedWriter writer;
+        private List<CloseableIterator> openedIterators = Lists.newArrayList();
+
+        public PersistentState(Comparator<String> comparator) {
+            this(comparator, createTempDir("oak-sorter-"));
+        }
+
+        public PersistentState(Comparator<String> comparator, File workDir) {
+            this.workDir = workDir;
+            this.comparator = comparator;
+        }
+
+        public BufferedWriter getWriter() throws FileNotFoundException {
+            if (idFile == null) {
+                idFile = new File(workDir, "strings.txt");
+                sortedFile = new File(workDir, "strings-sorted.txt");
+                writer = Files.newWriter(idFile, charset);
+            }
+            return writer;
+        }
+
+        public void sort() throws IOException {
+            closeWriter();
+
+            List<File> sortedFiles = ExternalSort.sortInBatch(idFile,
+                    comparator, //Comparator to use
+                    ExternalSort.DEFAULTMAXTEMPFILES,
+                    ExternalSort.DEFAULT_MAX_MEM_BYTES,
+                    charset, //charset
+                    workDir,  //temp directory where intermediate files are 
created
+                    true //distinct
+            );
+
+            ExternalSort.mergeSortedFiles(sortedFiles,
+                    sortedFile,
+                    comparator,
+                    charset,
+                    true
+            );
+        }
+
+        public Iterator<String> getIterator() throws IOException {
+            CloseableIterator itr = new 
CloseableIterator(Files.newReader(sortedFile, charset));
+            openedIterators.add(itr);
+            return itr;
+        }
+
+        @Override
+        public String toString() {
+            return "PersistentState : workDir=" + workDir.getAbsolutePath();
+        }
+
+        @Override
+        public void close() throws IOException {
+            Closer closer = Closer.create();
+            try {
+                closer.register(writer);
+                for (CloseableIterator citr : openedIterators) {
+                    closer.register(citr);
+                }
+                closer.register(new Closeable() {
+                    @Override
+                    public void close() throws IOException {
+                        FileUtils.deleteDirectory(workDir);
+                    }
+                });
+            } finally {
+                closer.close();
+            }
+        }
+
+        private void closeWriter() throws IOException {
+            writer.close();
+        }
+
+        /**
+         * Taken from com.google.common.io.Files#createTempDir()
+         * Modified to provide a prefix
+         */
+        private static File createTempDir(String prefix) {
+            File baseDir = new File(System.getProperty("java.io.tmpdir"));
+            String baseName = System.currentTimeMillis() + "-";
+
+            for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
+                File tempDir = new File(baseDir, prefix + baseName + counter);
+                if (tempDir.mkdir()) {
+                    return tempDir;
+                }
+            }
+            throw new IllegalStateException("Failed to create directory within 
"
+                    + TEMP_DIR_ATTEMPTS + " attempts (tried "
+                    + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) 
+ ')');
+        }
+    }
+
+    private static class CloseableIterator extends LineIterator implements 
Closeable {
+        public CloseableIterator(Reader reader) throws 
IllegalArgumentException {
+            super(reader);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/sort/StringSort.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java?rev=1666220&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java
 Thu Mar 12 15:22:46 2015
@@ -0,0 +1,144 @@
+/*
+ * 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.commons.sort;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class StringSortTest {
+    private Comparator<String> comparator = new PathComparator();
+    private StringSort collector;
+
+    @Test
+    public void inMemory() throws Exception{
+        List<String> paths = createTestPaths(5, false);
+        collector = new StringSort(paths.size() + 1,comparator);
+        addPathsToCollector(paths);
+
+        assertConstraints(paths);
+        assertFalse(collector.usingFile());
+        collector.close();
+    }
+
+    @Test
+    public void overflowToDisk() throws Exception{
+        //Create ~50k paths
+        List<String> paths = createTestPaths(10, true);
+        collector = new StringSort(1000, comparator);
+        addPathsToCollector(paths);
+
+        assertTrue(collector.usingFile());
+        assertConstraints(paths);
+
+        collector.close();
+    }
+
+    private void assertConstraints(List<String> paths) throws IOException {
+        assertEquals(paths.size(), collector.getSize());
+
+        Collections.sort(paths, comparator);
+        collector.sort();
+
+        List<String> sortedPaths = ImmutableList.copyOf(collector.getIds());
+        assertEquals(paths.size(), sortedPaths.size());
+        assertEquals(paths, sortedPaths);
+    }
+
+    private void addPathsToCollector(Iterable<String> paths) throws 
IOException {
+        for (String path : paths){
+            collector.add(path);
+        }
+    }
+
+    private static List<String> createTestPaths(int depth, boolean 
permutation){
+        List<String> rootPaths = Arrays.asList("a", "b", "c", "d", "e", "f", 
"g");
+        List<String> paths = new ArrayList<String>();
+
+
+        if (permutation){
+            List<String> newRoots = new ArrayList<String>();
+            for (List<String> permuts : 
Collections2.orderedPermutations(rootPaths)){
+                newRoots.add(Joiner.on("").join(permuts));
+            }
+            rootPaths = newRoots;
+        }
+
+        for (String root : rootPaths){
+            List<String> pathElements = new ArrayList<String>();
+            pathElements.add(root);
+            paths.add(createId(pathElements));
+            for (int i = 0; i < depth; i++){
+                pathElements.add(root + i);
+                paths.add(createId(pathElements));
+            }
+        }
+
+        Set<String> idSet = new HashSet<String>(paths);
+        assertEquals(paths.size(), idSet.size());
+
+        Collections.shuffle(paths);
+        return paths;
+    }
+
+    private static String createId(Iterable<String> pathElements){
+        return "/" + Joiner.on('/').join(pathElements);
+    }
+
+    private static  class PathComparator implements Comparator<String>, 
Serializable {
+        @Override
+        public int compare(String o1, String o2) {
+            int d1 = pathDepth(o1);
+            int d2 = pathDepth(o2);
+            if (d1 != d2) {
+                return Integer.signum(d2 - d1);
+            }
+            return o1.compareTo(o2);
+        }
+
+        private static int pathDepth(String path) {
+            if (path.equals("/")) {
+                return 0;
+            }
+            int depth = 0;
+            for (int i = 0; i < path.length(); i++) {
+                if (path.charAt(i) == '/') {
+                    depth++;
+                }
+            }
+            return depth;
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/sort/StringSortTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java?rev=1666220&r1=1666219&r2=1666220&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 Thu Mar 12 15:22:46 2015
@@ -574,7 +574,11 @@ public class DocumentNodeStoreService {
         RevisionGC revisionGC = new RevisionGC(new Runnable() {
             @Override
             public void run() {
-                store.getVersionGarbageCollector().gc(versionGcMaxAgeInSecs, 
TimeUnit.SECONDS);
+                try {
+                    
store.getVersionGarbageCollector().gc(versionGcMaxAgeInSecs, TimeUnit.SECONDS);
+                } catch (IOException e) {
+                    log.warn("Error occurred while executing the Version 
Garbage Collector", e);
+                }
             }
         }, executor);
         registrations.add(registerMBean(whiteboard, RevisionGCMBean.class, 
revisionGC,

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java?rev=1666220&r1=1666219&r2=1666220&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
 Thu Mar 12 15:22:46 2015
@@ -19,9 +19,9 @@
 
 package org.apache.jackrabbit.oak.plugins.document;
 
-import java.util.ArrayList;
-import java.util.Collections;
+import java.io.IOException;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -31,18 +31,23 @@ import com.google.common.base.StandardSy
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
 
+import org.apache.jackrabbit.oak.commons.sort.StringSort;
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static com.google.common.collect.Iterators.partition;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType.COMMIT_ROOT_ONLY;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType.DEFAULT_LEAF;
 
 public class VersionGarbageCollector {
+    //Kept less than MongoDocumentStore.IN_CLAUSE_BATCH_SIZE to avoid 
re-partitioning
+    private static final int DELETE_BATCH_SIZE = 450;
     private final DocumentNodeStore nodeStore;
     private final VersionGCSupport versionStore;
+    private int overflowToDiskThreshold = 100000;
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private static final Logger log = 
LoggerFactory.getLogger(VersionGarbageCollector.class);
 
     /**
      * Split document types which can be safely garbage collected
@@ -56,7 +61,7 @@ public class VersionGarbageCollector {
         this.versionStore = gcSupport;
     }
 
-    public VersionGCStats gc(long maxRevisionAge, TimeUnit unit) {
+    public VersionGCStats gc(long maxRevisionAge, TimeUnit unit) throws 
IOException {
         long maxRevisionAgeInMillis = unit.toMillis(maxRevisionAge);
         Stopwatch sw = Stopwatch.createStarted();
         VersionGCStats stats = new VersionGCStats();
@@ -85,41 +90,60 @@ public class VersionGarbageCollector {
         return stats;
     }
 
+    public void setOverflowToDiskThreshold(int overflowToDiskThreshold) {
+        this.overflowToDiskThreshold = overflowToDiskThreshold;
+    }
+
     private void collectSplitDocuments(VersionGCStats stats, long 
oldestRevTimeStamp) {
         versionStore.deleteSplitDocuments(GC_TYPES, oldestRevTimeStamp, stats);
     }
 
-    private void collectDeletedDocuments(VersionGCStats stats, Revision 
headRevision, long oldestRevTimeStamp) {
-        List<String> docIdsToDelete = new ArrayList<String>();
-        Iterable<NodeDocument> itr = 
versionStore.getPossiblyDeletedDocs(oldestRevTimeStamp);
+    private void collectDeletedDocuments(VersionGCStats stats, Revision 
headRevision, long oldestRevTimeStamp)
+            throws IOException {
+        StringSort docIdsToDelete = new StringSort(overflowToDiskThreshold, 
NodeDocumentIdComparator.INSTANCE);
         try {
-            for (NodeDocument doc : itr) {
-                //Check if node is actually deleted at current revision
-                //As node is not modified since oldestRevTimeStamp then
-                //this node has not be revived again in past maxRevisionAge
-                //So deleting it is safe
-                if (doc.getNodeAtRevision(nodeStore, headRevision, null) == 
null) {
-                    docIdsToDelete.add(doc.getId());
-                    //Collect id of all previous docs also
-                    for (NodeDocument prevDoc : 
ImmutableList.copyOf(doc.getAllPreviousDocs())) {
-                        docIdsToDelete.add(prevDoc.getId());
+            Iterable<NodeDocument> itr = 
versionStore.getPossiblyDeletedDocs(oldestRevTimeStamp);
+            try {
+                for (NodeDocument doc : itr) {
+                    //Check if node is actually deleted at current revision
+                    //As node is not modified since oldestRevTimeStamp then
+                    //this node has not be revived again in past maxRevisionAge
+                    //So deleting it is safe
+                    if (doc.getNodeAtRevision(nodeStore, headRevision, null) 
== null) {
+                        docIdsToDelete.add(doc.getId());
+                        //Collect id of all previous docs also
+                        for (NodeDocument prevDoc : 
ImmutableList.copyOf(doc.getAllPreviousDocs())) {
+                            docIdsToDelete.add(prevDoc.getId());
+                        }
                     }
                 }
+            } finally {
+                Utils.closeIfCloseable(itr);
+            }
+
+            if (docIdsToDelete.isEmpty()){
+                return;
             }
-        } finally {
-            Utils.closeIfCloseable(itr);
-        }
 
-        Collections.sort(docIdsToDelete, NodeDocumentIdComparator.INSTANCE);
+            docIdsToDelete.sort();
+            log.info("Proceeding to delete [{}] documents", 
docIdsToDelete.getSize());
 
-        if(log.isDebugEnabled()) {
-            StringBuilder sb = new StringBuilder("Deleted document with 
following ids were deleted as part of GC \n");
-            
Joiner.on(StandardSystemProperty.LINE_SEPARATOR.value()).appendTo(sb, 
docIdsToDelete);
-            log.debug(sb.toString());
+            if (log.isDebugEnabled() && docIdsToDelete.getSize() < 1000) {
+                StringBuilder sb = new StringBuilder("Deleted document with 
following ids were deleted as part of GC \n");
+                
Joiner.on(StandardSystemProperty.LINE_SEPARATOR.value()).appendTo(sb, 
docIdsToDelete.getIds());
+                log.debug(sb.toString());
+            }
+
+            Iterator<List<String>> idListItr = 
partition(docIdsToDelete.getIds(), DELETE_BATCH_SIZE);
+            while (idListItr.hasNext()) {
+                nodeStore.getDocumentStore().remove(Collection.NODES, 
idListItr.next());
+            }
+
+            nodeStore.invalidateDocChildrenCache();
+            stats.deletedDocGCCount += docIdsToDelete.getSize();
+        } finally {
+            docIdsToDelete.close();
         }
-        nodeStore.getDocumentStore().remove(Collection.NODES, docIdsToDelete);
-        nodeStore.invalidateDocChildrenCache();
-        stats.deletedDocGCCount += docIdsToDelete.size();
     }
 
     public static class VersionGCStats {

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java?rev=1666220&r1=1666219&r2=1666220&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java
 Thu Mar 12 15:22:46 2015
@@ -36,6 +36,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static java.util.concurrent.TimeUnit.HOURS;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
@@ -104,6 +105,53 @@ public class VersionGCDeletionTest {
         assertNull(ts.find(Collection.NODES, "1:/x"));
     }
 
+    @Test
+    public void deleteLargeNumber() throws Exception{
+        int noOfDocsToDelete = 10000;
+        DocumentStore ts = new MemoryDocumentStore();
+        store = new DocumentMK.Builder()
+                .clock(clock)
+                .setDocumentStore(new MemoryDocumentStore())
+                .setAsyncDelay(0)
+                .getNodeStore();
+
+        //Baseline the clock
+        clock.waitUntil(Revision.getCurrentTimestamp());
+
+        NodeBuilder b1 = store.getRoot().builder();
+        NodeBuilder xb = b1.child("x");
+        for (int i = 0; i < noOfDocsToDelete; i++){
+            xb.child("a"+i).child("b"+i);
+        }
+        store.merge(b1, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        long maxAge = 1; //hours
+        long delta = TimeUnit.MINUTES.toMillis(10);
+
+        //Remove x/y
+        NodeBuilder b2 = store.getRoot().builder();
+        b2.child("x").remove();
+        store.merge(b2, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        store.runBackgroundOperations();
+
+        //3. Check that deleted doc does get collected post maxAge
+        clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
+        VersionGarbageCollector gc = store.getVersionGarbageCollector();
+        gc.setOverflowToDiskThreshold(100);
+
+        VersionGarbageCollector.VersionGCStats stats = gc.gc(maxAge * 2, 
HOURS);
+        assertEquals(noOfDocsToDelete * 2 + 1, stats.deletedDocGCCount);
+
+
+        assertNull(ts.find(Collection.NODES, "1:/x"));
+
+        for (int i = 0; i < noOfDocsToDelete; i++){
+            assertNull(ts.find(Collection.NODES, "2:/a"+i+"/b"+i));
+            assertNull(ts.find(Collection.NODES, "1:/a"+i));
+        }
+    }
+
     private static class TestDocumentStore extends MemoryDocumentStore {
         boolean throwException;
         @Override

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCWithSplitTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCWithSplitTest.java?rev=1666220&r1=1666219&r2=1666220&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCWithSplitTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCWithSplitTest.java
 Thu Mar 12 15:22:46 2015
@@ -137,7 +137,11 @@ public class VersionGCWithSplitTest {
         Thread t = new Thread(new Runnable() {
             @Override
             public void run() {
-                stats.set(gc.gc(1, HOURS));
+                try {
+                    stats.set(gc.gc(1, HOURS));
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
             }
         });
 


Reply via email to