Author: mduerig
Date: Tue Jan 16 14:28:02 2018
New Revision: 1821249

URL: http://svn.apache.org/viewvc?rev=1821249&view=rev
Log:
OAK-7132: SNFE after full compaction
Respect number of retained generations on full compaction

Added:
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/GCGenerationTest.java
Modified:
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/Reclaimers.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GCGeneration.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/ReclaimersTest.java

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java?rev=1821249&r1=1821248&r2=1821249&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
 Tue Jan 16 14:28:02 2018
@@ -30,6 +30,8 @@ import static org.apache.jackrabbit.oak.
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 import static 
org.apache.jackrabbit.oak.segment.DefaultSegmentWriterBuilder.defaultSegmentWriterBuilder;
 import static org.apache.jackrabbit.oak.segment.SegmentId.isDataSegmentId;
+import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.FULL;
+import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.TAIL;
 import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.CLEANUP;
 import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION;
 import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION_FORCE_COMPACT;
@@ -37,6 +39,7 @@ import static org.apache.jackrabbit.oak.
 import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.ESTIMATION;
 import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.IDLE;
 import static 
org.apache.jackrabbit.oak.segment.file.PrintableBytes.newPrintableBytes;
+import static 
org.apache.jackrabbit.oak.segment.file.Reclaimers.newOldReclaimer;
 import static 
org.apache.jackrabbit.oak.segment.file.TarRevisions.EXPEDITE_OPTION;
 import static org.apache.jackrabbit.oak.segment.file.TarRevisions.timeout;
 
@@ -76,6 +79,7 @@ import org.apache.jackrabbit.oak.segment
 import org.apache.jackrabbit.oak.segment.SegmentWriter;
 import org.apache.jackrabbit.oak.segment.WriterCacheManager;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
 import org.apache.jackrabbit.oak.segment.file.GCJournal.GCJournalEntry;
 import org.apache.jackrabbit.oak.segment.file.ShutDown.ShutDownCloser;
 import org.apache.jackrabbit.oak.segment.file.tar.CleanupContext;
@@ -384,11 +388,7 @@ public class FileStore extends AbstractF
      */
     public void cleanup() throws IOException {
         try (ShutDownCloser ignored = shutDown.keepAlive()) {
-            fileReaper.add(garbageCollector.cleanup(CompactionResult.skipped(
-                getGcGeneration(),
-                garbageCollector.gcOptions,
-                revisions.getHead()
-            )));
+            fileReaper.add(garbageCollector.cleanup());
         }
     }
 
@@ -576,6 +576,15 @@ public class FileStore extends AbstractF
          */
         private long lastSuccessfullGC;
 
+        /**
+         * Last compaction type used to determine which predicate to use during
+         * {@link #cleanup() cleanup}. Defaults to {@link GCType#FULL FULL}, 
which is
+         * conservative and safe in case it does not match the real type (e.g. 
because
+         * of a system restart).
+         */
+        @Nonnull
+        private GCType lastCompactionType = FULL;
+
         GarbageCollector(
                 @Nonnull SegmentGCOptions gcOptions,
                 @Nonnull GCListener gcListener,
@@ -684,9 +693,12 @@ public class FileStore extends AbstractF
         }
 
         @Nonnull
-        private CompactionResult compactionSucceeded(@Nonnull GCGeneration 
generation, @Nonnull RecordId compactedRootId) {
+        private CompactionResult compactionSucceeded(
+                @Nonnull GCType gcType,
+                @Nonnull GCGeneration generation,
+                @Nonnull RecordId compactedRootId) {
             gcListener.compactionSucceeded(generation);
-            return CompactionResult.succeeded(generation, gcOptions, 
compactedRootId);
+            return CompactionResult.succeeded(gcType, generation, gcOptions, 
compactedRootId);
         }
 
         @CheckForNull
@@ -708,20 +720,21 @@ public class FileStore extends AbstractF
 
         synchronized CompactionResult compactFull() {
             gcListener.info("running full compaction");
-            return compact(EMPTY_NODE, getGcGeneration().nextFull());
+            return compact(FULL, EMPTY_NODE, getGcGeneration().nextFull());
         }
 
         synchronized CompactionResult compactTail() {
             gcListener.info("running tail compaction");
             SegmentNodeState base = getBase();
             if (base != null) {
-                return compact(base, getGcGeneration().nextTail());
+                return compact(TAIL, base, getGcGeneration().nextTail());
             }
             gcListener.info("no base state available, running full compaction 
instead");
-            return compact(EMPTY_NODE, getGcGeneration().nextFull());
+            return compact(FULL, EMPTY_NODE, getGcGeneration().nextFull());
         }
 
         private CompactionResult compact(
+                @Nonnull GCType gcType,
                 @Nonnull NodeState base,
                 @Nonnull GCGeneration newGeneration) {
             try {
@@ -817,10 +830,12 @@ public class FileStore extends AbstractF
                 }
 
                 if (success) {
+                    // Update type of the last compaction before calling 
methods that could throw an exception.
+                    lastCompactionType = gcType;
                     writer.flush();
                     flush();
                     gcListener.info("compaction succeeded in {}, after {} 
cycles", watch, cycles);
-                    return compactionSucceeded(newGeneration, 
compacted.getRecordId());
+                    return compactionSucceeded(gcType, newGeneration, 
compacted.getRecordId());
                 } else {
                     gcListener.info("compaction failed after {}, and {} 
cycles", watch, cycles);
                     return compactionAborted(newGeneration);
@@ -902,6 +917,22 @@ public class FileStore extends AbstractF
         }
 
         /**
+         * Cleanup segments whose generation matches the reclaim predicate 
determined by
+         * the {@link #lastCompactionType last successful compaction}.
+         * @return list of files to be removed
+         * @throws IOException
+         */
+        @Nonnull
+        synchronized List<File> cleanup() throws IOException {
+            return cleanup(CompactionResult.skipped(
+                lastCompactionType,
+                getGcGeneration(),
+                garbageCollector.gcOptions,
+                revisions.getHead()
+            ));
+        }
+
+        /**
          * Cleanup segments whose generation matches the {@link 
CompactionResult#reclaimer()} predicate.
          * @return list of files to be removed
          * @throws IOException
@@ -911,7 +942,7 @@ public class FileStore extends AbstractF
             throws IOException {
             PrintableStopwatch watch = PrintableStopwatch.createStarted();
 
-            gcListener.info("cleanup started.");
+            gcListener.info("cleanup started using reclaimer {}", 
compactionResult.reclaimer());
             gcListener.updateStatus(CLEANUP.message());
             segmentCache.clear();
 
@@ -964,7 +995,7 @@ public class FileStore extends AbstractF
         synchronized void collectBlobReferences(Consumer<String> collector) 
throws IOException {
             segmentWriter.flush();
             tarFiles.collectBlobReferences(collector,
-                    Reclaimers.newOldReclaimer(getGcGeneration(), 
gcOptions.getRetainedGenerations()));
+                    newOldReclaimer(lastCompactionType, getGcGeneration(), 
gcOptions.getRetainedGenerations()));
         }
 
         void cancel() {
@@ -1038,9 +1069,9 @@ public class FileStore extends AbstractF
 
     /**
      * Instances of this class represent the result from a compaction. Either
-     * {@link #succeeded(GCGeneration, SegmentGCOptions, RecordId) succeeded},
+     * {@link #succeeded(GCType, GCGeneration, SegmentGCOptions, RecordId) 
succeeded},
      * {@link #aborted(GCGeneration, GCGeneration) aborted} or {@link
-     * #skipped(GCGeneration, SegmentGCOptions, RecordId)}  skipped}.
+     * #skipped(GCType, GCGeneration, SegmentGCOptions, RecordId)}  skipped}.
      */
     private abstract static class CompactionResult {
         @Nonnull
@@ -1052,18 +1083,20 @@ public class FileStore extends AbstractF
 
         /**
          * Result of a succeeded compaction.
+         * @param gcType            the type of the succeeded compaction 
operation
          * @param newGeneration     the generation successfully created by 
compaction
          * @param gcOptions         the current GC options used by compaction
          * @param compactedRootId   the record id of the root created by 
compaction
          */
         static CompactionResult succeeded(
+                @Nonnull GCType gcType,
                 @Nonnull GCGeneration newGeneration,
                 @Nonnull final SegmentGCOptions gcOptions,
                 @Nonnull final RecordId compactedRootId) {
             return new CompactionResult(newGeneration) {
                 @Override
                 Predicate<GCGeneration> reclaimer() {
-                    return Reclaimers.newOldReclaimer(newGeneration, 
gcOptions.getRetainedGenerations());
+                    return newOldReclaimer(gcType, newGeneration, 
gcOptions.getRetainedGenerations());
                 }
 
                 @Override
@@ -1101,17 +1134,19 @@ public class FileStore extends AbstractF
 
         /**
          * Result serving as a placeholder for a compaction that was skipped.
+         * @param lastGCType         type of the most recent gc operation. 
{@link GCType#FULL} if none.
          * @param currentGeneration  the current generation of the store
-         * @param gcOptions         the current GC options used by compaction
+         * @param gcOptions          the current GC options used by compaction
          */
         static CompactionResult skipped(
+                @Nonnull GCType lastGCType,
                 @Nonnull GCGeneration currentGeneration,
                 @Nonnull final SegmentGCOptions gcOptions,
                 @Nonnull final RecordId compactedRootId) {
             return new CompactionResult(currentGeneration) {
                 @Override
                 Predicate<GCGeneration> reclaimer() {
-                    return Reclaimers.newOldReclaimer(currentGeneration, 
gcOptions.getRetainedGenerations());
+                    return Reclaimers.newOldReclaimer(lastGCType, 
currentGeneration, gcOptions.getRetainedGenerations());
                 }
 
                 @Override
@@ -1134,8 +1169,8 @@ public class FileStore extends AbstractF
         abstract Predicate<GCGeneration> reclaimer();
 
         /**
-         * @return  {@code true} for {@link #succeeded(GCGeneration, 
SegmentGCOptions, RecordId) succeeded}
-         *          and {@link #skipped(GCGeneration, SegmentGCOptions, 
RecordId) skipped}, {@code false} otherwise.
+         * @return  {@code true} for {@link #succeeded(GCType, GCGeneration, 
SegmentGCOptions, RecordId) succeeded}
+         *          and {@link #skipped(GCType, GCGeneration, 
SegmentGCOptions, RecordId) skipped}, {@code false} otherwise.
          */
         abstract boolean isSuccess();
 

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/Reclaimers.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/Reclaimers.java?rev=1821249&r1=1821248&r2=1821249&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/Reclaimers.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/Reclaimers.java
 Tue Jan 16 14:28:02 2018
@@ -17,18 +17,93 @@
 
 package org.apache.jackrabbit.oak.segment.file;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import javax.annotation.Nonnull;
 
 import com.google.common.base.Predicate;
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
 import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
 
+/**
+ * Helper class exposing static factories for reclaimers. A reclaimer
+ * is a predicate used during the cleanup phase of garbage collection
+ * to decide whether a segment of a given generation is reclaimable.
+ */
 class Reclaimers {
 
     private Reclaimers() {
         // Prevent instantiation.
     }
 
+    /**
+     * Create a reclaimer for segments of old generations. Whether a segment 
is considered old and
+     * thus reclaimable depends on the type of the most recent GC operation 
and the number of
+     * retained generations.
+     * <p>
+     * In the case of {@link GCType#FULL FULL} a segment is reclaimable if its
+     * {@link GCGeneration#getFullGeneration() full generation} is at least 
{@code retainedGenerations}
+     * in the past wrt. {@code referenceGeneration} <em>or</em> if its
+     * {@link GCGeneration#getGeneration() generation} is at least {@code 
retainedGenerations} in the
+     * past wrt. {@code referenceGeneration} and it not a {@link 
GCGeneration#isCompacted() compacted segment}.
+     * <p>
+     * In the case of {@link GCType#TAIL TAIL} a segment is reclaimable if its
+     * {@link GCGeneration#getGeneration() generation} is at least {@code 
retainedGenerations} in the
+     * past wrt. {@code referenceGeneration} <em>and</em> the segment is not 
in the same tail as
+     * segments of the {@code referenceGeneration}. A segment is in the same 
tail as another segment
+     * if it is a {@link GCGeneration#isCompacted() compacted segment} 
<em>and</em> both segments have
+     * the same {@link GCGeneration#fullGeneration full generation}.
+     *
+     * @param lastGCType  type of the most recent GC operation. {@link 
GCType#FULL} if unknown.
+     * @param referenceGeneration  generation used as reference for 
determining the age of other segments.
+     * @param retainedGenerations  number of generations to retain.
+     */
     static Predicate<GCGeneration> newOldReclaimer(
+            @Nonnull GCType lastGCType,
+            @Nonnull final GCGeneration referenceGeneration,
+            int retainedGenerations) {
+
+        switch (checkNotNull(lastGCType)) {
+            case FULL:
+                return newOldFullReclaimer(referenceGeneration, 
retainedGenerations);
+            case TAIL:
+                return newOldTailReclaimer(referenceGeneration, 
retainedGenerations);
+            default:
+                throw new IllegalArgumentException("Invalid gc type: " + 
lastGCType);
+        }
+    }
+
+    private static Predicate<GCGeneration> newOldFullReclaimer(
+            @Nonnull final GCGeneration referenceGeneration,
+            int retainedGenerations) {
+        return new Predicate<GCGeneration>() {
+
+            @Override
+            public boolean apply(GCGeneration generation) {
+                return isOldFull(generation) || (isOld(generation) && 
!generation.isCompacted());
+            }
+
+            private boolean isOld(GCGeneration generation) {
+                return referenceGeneration.compareWith(generation) >= 
retainedGenerations;
+            }
+
+            private boolean isOldFull(GCGeneration generation) {
+                return 
referenceGeneration.compareFullGenerationWith(generation) >= 
retainedGenerations;
+            }
+
+            @Override
+            public String toString() {
+                return String.format(
+                        "(full generation older than %d.%d, with %d retained 
generations)",
+                        referenceGeneration.getGeneration(),
+                        referenceGeneration.getFullGeneration(),
+                        retainedGenerations
+                );
+            }
+        };
+    }
+
+    private static Predicate<GCGeneration> newOldTailReclaimer(
             @Nonnull final GCGeneration referenceGeneration,
             int retainedGenerations) {
         return new Predicate<GCGeneration>() {
@@ -50,7 +125,7 @@ class Reclaimers {
             @Override
             public String toString() {
                 return String.format(
-                        "(generation older than %d.%d, with %d retained 
generations)",
+                        "(generation older than %d.%d, with %d retained 
generations and not in the same compacted tail)",
                         referenceGeneration.getGeneration(),
                         referenceGeneration.getFullGeneration(),
                         retainedGenerations
@@ -60,6 +135,12 @@ class Reclaimers {
         };
     }
 
+    /**
+     * Create an exact reclaimer. An exact reclaimer reclaims only segment of 
on single generation.
+     * @param referenceGeneration  the generation to collect.
+     * @return  an new instance of an exact reclaimer for segments with their 
generation
+     *          matching {@code referenceGeneration}.
+     */
     static Predicate<GCGeneration> newExactReclaimer(@Nonnull final 
GCGeneration referenceGeneration) {
         return new Predicate<GCGeneration>() {
             @Override

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GCGeneration.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GCGeneration.java?rev=1821249&r1=1821248&r2=1821249&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GCGeneration.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GCGeneration.java
 Tue Jan 16 14:28:02 2018
@@ -108,10 +108,25 @@ public final class GCGeneration {
         return new GCGeneration(generation, fullGeneration, false);
     }
 
+    /**
+     * Compare this generation with {@code gcGeneration}
+     * @param gcGeneration  the generation this generation is compared against.
+     * @return  Number of generations between this generation and {@code 
gcGeneration}
+     */
     public int compareWith(@Nonnull GCGeneration gcGeneration) {
         return generation - checkNotNull(gcGeneration).generation;
     }
 
+    /**
+     * Compare this full generation the full generation of {@code gcGeneration}
+     * @param gcGeneration  the generation this generation is compared against.
+     * @return  Number of generations between the full generations of this 
generation
+     *          and {@code gcGeneration}
+     */
+    public int compareFullGenerationWith(@Nonnull GCGeneration gcGeneration) {
+        return fullGeneration - checkNotNull(gcGeneration).fullGeneration;
+    }
+
     @Override
     public boolean equals(Object other) {
         if (this == other) {

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.java?rev=1821249&r1=1821248&r2=1821249&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.java
 Tue Jan 16 14:28:02 2018
@@ -84,7 +84,6 @@ import org.apache.jackrabbit.oak.stats.C
 import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider;
 import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -1411,7 +1410,6 @@ public class CompactionAndCleanupIT {
         }
     }
 
-    @Ignore("OAK-7132")  // FIXME OAK-7132: SNFE after full compaction
     @Test
     public void latestFullCompactedStateShouldNotBeDeleted() throws Exception {
         SegmentGCOptions gcOptions = defaultGCOptions()

Modified: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/ReclaimersTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/ReclaimersTest.java?rev=1821249&r1=1821248&r2=1821249&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/ReclaimersTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/ReclaimersTest.java
 Tue Jan 16 14:28:02 2018
@@ -17,91 +17,178 @@
 
 package org.apache.jackrabbit.oak.segment.file;
 
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.join;
+import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.FULL;
+import static 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.TAIL;
 import static 
org.apache.jackrabbit.oak.segment.file.Reclaimers.newExactReclaimer;
 import static 
org.apache.jackrabbit.oak.segment.file.Reclaimers.newOldReclaimer;
 import static 
org.apache.jackrabbit.oak.segment.file.tar.GCGeneration.newGCGeneration;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
 import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
 import org.junit.Test;
 
 public class ReclaimersTest {
 
-    private static void testOldReclaimer(boolean isCompacted) {
-        Predicate<GCGeneration> reclaimer = newOldReclaimer(newGCGeneration(3, 
3, isCompacted), 2);
-
-        // Don't reclaim young segments
-        assertFalse(reclaimer.apply(newGCGeneration(3, 3, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(3, 3, true)));
-        assertFalse(reclaimer.apply(newGCGeneration(3, 3, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(3, 3, true)));
-        assertFalse(reclaimer.apply(newGCGeneration(2, 3, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(2, 3, true)));
-        assertFalse(reclaimer.apply(newGCGeneration(2, 3, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(2, 3, true)));
-
-        // Reclaim old and uncompacted segments
-        assertTrue(reclaimer.apply(newGCGeneration(1, 3, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(0, 3, false)));
-
-        // Don't reclaim old compacted segments from the same full generation
-        assertFalse(reclaimer.apply(newGCGeneration(1, 3, true)));
-        assertFalse(reclaimer.apply(newGCGeneration(0, 3, true)));
-
-        // Reclaim old compacted segments from prior full generations
-        assertTrue(reclaimer.apply(newGCGeneration(1, 2, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(1, 2, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(0, 2, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(0, 2, false)));
-    }
-
-    private static void testOldReclaimerSequence(boolean isCompacted) {
-        Predicate<GCGeneration> reclaimer = newOldReclaimer(newGCGeneration(9, 
2, isCompacted), 2);
-
-        assertFalse(reclaimer.apply(newGCGeneration(9, 2, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(9, 2, true)));
-        assertFalse(reclaimer.apply(newGCGeneration(8, 2, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(8, 2, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(7, 2, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(7, 2, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(6, 2, false)));
-        assertFalse(reclaimer.apply(newGCGeneration(6, 2, true)));
-
-        assertTrue(reclaimer.apply(newGCGeneration(5, 1, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(5, 1, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(4, 1, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(4, 1, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(3, 1, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(3, 1, true)));
-
-        assertTrue(reclaimer.apply(newGCGeneration(2, 0, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(2, 0, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(1, 0, false)));
-        assertTrue(reclaimer.apply(newGCGeneration(1, 0, true)));
-        assertTrue(reclaimer.apply(newGCGeneration(0, 0, false)));
+    private static final Map<String, GCGeneration> gcHistory = 
ImmutableMap.<String, GCGeneration>builder()
+        .put("00w", newGCGeneration(0, 0, false))
 
+        // First compaction. Always FULL
+        .put("11c", newGCGeneration(1, 1, true))
+        .put("11w", newGCGeneration(1, 1, false))
+
+        // TAIL compaction
+        .put("21c", newGCGeneration(2, 1, true))
+        .put("21w", newGCGeneration(2, 1, false))
+
+        // TAIL compaction
+        .put("31c", newGCGeneration(3, 1, true))
+        .put("31w", newGCGeneration(3, 1, false))
+
+        // FULL compaction
+        .put("42c", newGCGeneration(4, 2, true))
+        .put("42w", newGCGeneration(4, 2, false))
+
+        // TAIL compaction
+        .put("52c", newGCGeneration(5, 2, true))
+        .put("52w", newGCGeneration(5, 2, false))
+
+        // TAIL compaction
+        .put("62c", newGCGeneration(6, 2, true))
+        .put("62w", newGCGeneration(6, 2, false))
+
+        // FULL compaction
+        .put("73c", newGCGeneration(7, 3, true))
+        .put("73w", newGCGeneration(7, 3, false))
+
+        .build();
+
+    private static void assertReclaim(Predicate<GCGeneration> reclaimer, 
String... reclaims) {
+        Set<String> toReclaim = newHashSet(reclaims);
+        for (Entry<String, GCGeneration> generation : gcHistory.entrySet()) {
+            if (reclaimer.apply(generation.getValue())) {
+                assertTrue(
+                    reclaimer + " should not reclaim " + generation.getKey(),
+                    toReclaim.remove(generation.getKey()));
+            }
+        }
+
+        if (!toReclaim.isEmpty()) {
+            fail(reclaimer + " failed to reclaim " + join(",", toReclaim));
+        }
     }
 
     @Test
-    public void testOldReclaimerCompactedHead() {
-        testOldReclaimer(true);
-    }
-
-    @Test
-    public void testOldReclaimerUncompactedHead() {
-        testOldReclaimer(false);
-    }
-
-    @Test
-    public void testOldReclaimerSequenceCompactedHead() throws Exception {
-        testOldReclaimerSequence(true);
+    public void testOldReclaimer() {
+        // 1 retained generation
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(0, 0, false), 1));
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(1, 1, false), 1),
+            "00w");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(2, 1, false), 1),
+            "00w", "11w");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(3, 1, false), 1),
+            "00w", "11w", "21w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(4, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(5, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(6, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "52w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(7, 3, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "42c", 
"52w", "52c", "62w", "62c");
+
+        // 2 retained generation
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(0, 0, false), 2));
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(1, 1, false), 2));
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(2, 1, false), 2),
+            "00w");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(3, 1, false), 2),
+            "00w", "11w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(4, 2, false), 2),
+            "00w", "11w", "21w");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(5, 2, false), 2),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c");
+        assertReclaim(newOldReclaimer(TAIL,
+            newGCGeneration(6, 2, false), 2),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(7, 3, false), 2),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "52w");
     }
 
     @Test
-    public void testOldReclaimerSequenceUncompactedHead() throws Exception {
-        testOldReclaimerSequence(false);
+    public void testOldReclaimerDefaultingToFull() {
+        // 1 retained generation
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(0, 0, false), 1));
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(1, 1, false), 1),
+            "00w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(2, 1, false), 1),
+            "00w", "11w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(3, 1, false), 1),
+            "00w", "11w", "21w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(4, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(5, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(6, 2, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "52w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(7, 3, false), 1),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "42c", 
"52w", "52c", "62w", "62c");
+
+        // 2 retained generation
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(0, 0, false), 2));
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(1, 1, false), 2));
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(2, 1, false), 2),
+            "00w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(3, 1, false), 2),
+            "00w", "11w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(4, 2, false), 2),
+            "00w", "11w", "21w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(5, 2, false), 2),
+            "00w", "11w", "21w", "31w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(6, 2, false), 2),
+            "00w", "11w", "21w", "31w", "42w");
+        assertReclaim(newOldReclaimer(FULL,
+            newGCGeneration(7, 3, false), 2),
+            "00w", "11w", "11c", "21w", "21c", "31w", "31c", "42w", "52w");
     }
 
     @Test

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/GCGenerationTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/GCGenerationTest.java?rev=1821249&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/GCGenerationTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/GCGenerationTest.java
 Tue Jan 16 14:28:02 2018
@@ -0,0 +1,41 @@
+/*
+ * 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.segment.file.tar;
+
+import static 
org.apache.jackrabbit.oak.segment.file.tar.GCGeneration.newGCGeneration;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class GCGenerationTest {
+
+    @Test
+    public void testCompareWith() {
+        GCGeneration m = newGCGeneration(0, 0, false);
+        GCGeneration n = newGCGeneration(2, 3, false);
+        assertEquals(2, n.compareWith(m));
+    }
+
+    @Test
+    public void testCompareFullGenerationWith() {
+        GCGeneration m = newGCGeneration(0, 0, false);
+        GCGeneration n = newGCGeneration(2, 3, false);
+        assertEquals(3, n.compareFullGenerationWith(m));
+    }
+}
\ No newline at end of file


Reply via email to