You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by fr...@apache.org on 2018/04/23 15:15:46 UTC

svn commit: r1829894 - /jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/

Author: frm
Date: Mon Apr 23 15:15:46 2018
New Revision: 1829894

URL: http://svn.apache.org/viewvc?rev=1829894&view=rev
Log:
OAK-7434 - Extract compaction implementations in separate components

Introduce CompactionStrategy, which represents a possible way to carry on the
compaction phase in the scope of the bigger garbage collection process. The
full and tail compaction phases have been extracted to FullCompactionStrategy
and TailCompactionStrategy respectively. Moreover, in order to have full
compaction act as a fallback for tail compaction when a base state can't be
found, CompactionResult has been extended with a new result type and
FallbackCompactionStrategy has been introduced.

Added:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java   (with props)
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionResult.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/DefaultGarbageCollectionStrategy.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollectionStrategy.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollector.java

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,233 @@
+/*
+ * 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;
+
+import static java.lang.Thread.currentThread;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION;
+import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION_FORCE_COMPACT;
+import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION_RETRY;
+import static org.apache.jackrabbit.oak.segment.file.TarRevisions.EXPEDITE_OPTION;
+import static org.apache.jackrabbit.oak.segment.file.TarRevisions.timeout;
+
+import java.io.IOException;
+
+import com.google.common.base.Function;
+import org.apache.jackrabbit.oak.segment.CheckpointCompactor;
+import org.apache.jackrabbit.oak.segment.RecordId;
+import org.apache.jackrabbit.oak.segment.SegmentNodeState;
+import org.apache.jackrabbit.oak.segment.SegmentWriter;
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
+import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+abstract class AbstractCompactionStrategy implements CompactionStrategy {
+
+    abstract GCType getCompactionType();
+
+    abstract GCGeneration nextGeneration(GCGeneration current);
+
+    private CompactionResult compactionSucceeded(
+        Context context,
+        GCGeneration generation,
+        RecordId compactedRootId
+    ) {
+        context.getGCListener().compactionSucceeded(generation);
+        return CompactionResult.succeeded(getCompactionType(), generation, context.getGCOptions(), compactedRootId, context.getGCCount());
+    }
+
+    private static GCGeneration getGcGeneration(Context context) {
+        return context.getRevisions().getHead().getSegmentId().getGcGeneration();
+    }
+
+    private static SegmentNodeState getHead(Context context) {
+        return context.getSegmentReader().readHeadState(context.getRevisions());
+    }
+
+    private static long size(Context context) {
+        return context.getTarFiles().size();
+    }
+
+    private static CompactionResult compactionAborted(Context context, GCGeneration generation) {
+        context.getGCListener().compactionFailed(generation);
+        return CompactionResult.aborted(getGcGeneration(context), generation, context.getGCCount());
+    }
+
+    private static SegmentNodeState forceCompact(
+        Context context,
+        NodeState base,
+        NodeState onto,
+        CheckpointCompactor compactor
+    ) throws InterruptedException {
+        RecordId compactedId = setHead(context, headId -> {
+            try {
+                PrintableStopwatch t = PrintableStopwatch.createStarted();
+                SegmentNodeState after = compactor.compact(base, context.getSegmentReader().readNode(headId), onto);
+                if (after != null) {
+                    return after.getRecordId();
+                }
+                context.getGCListener().info("compaction cancelled after {}", t);
+                return null;
+            } catch (IOException e) {
+                context.getGCListener().error("error during forced compaction.", e);
+                return null;
+            }
+        });
+        if (compactedId == null) {
+            return null;
+        }
+        return context.getSegmentReader().readNode(compactedId);
+    }
+
+    private static RecordId setHead(Context context, Function<RecordId, RecordId> f) throws InterruptedException {
+        return context.getRevisions().setHead(f, timeout(context.getGCOptions().getForceTimeout(), SECONDS));
+    }
+
+    private static String formatCompactionType(GCType compactionType) {
+        switch (compactionType) {
+            case FULL:
+                return "full";
+            case TAIL:
+                return "tail";
+            default:
+                throw new IllegalStateException("unsupported compaction type: " + compactionType);
+        }
+    }
+
+    final CompactionResult compact(Context context, NodeState base) {
+        context.getGCListener().info("running {} compaction", formatCompactionType(getCompactionType()));
+
+        GCGeneration nextGeneration = nextGeneration(getGcGeneration(context));
+
+        try {
+            PrintableStopwatch watch = PrintableStopwatch.createStarted();
+            context.getGCListener().info(
+                "compaction started, gc options={}, current generation={}, new generation={}",
+                context.getGCOptions(),
+                getHead(context).getRecordId().getSegment().getGcGeneration(),
+                nextGeneration
+            );
+            context.getGCListener().updateStatus(COMPACTION.message());
+
+            GCJournal.GCJournalEntry gcEntry = context.getGCJournal().read();
+            long initialSize = size(context);
+
+            SegmentWriter writer = context.getSegmentWriterFactory().newSegmentWriter(nextGeneration);
+
+            context.getCompactionMonitor().init(gcEntry.getRepoSize(), gcEntry.getNodes(), initialSize);
+
+            CheckpointCompactor compactor = new CheckpointCompactor(
+                context.getGCListener(),
+                context.getSegmentReader(),
+                writer,
+                context.getBlobStore(),
+                context.getCanceller(),
+                context.getCompactionMonitor()
+            );
+
+            SegmentNodeState head = getHead(context);
+            SegmentNodeState compacted = compactor.compact(base, head, base);
+            if (compacted == null) {
+                context.getGCListener().warn("compaction cancelled: {}.", context.getCanceller());
+                return compactionAborted(context, nextGeneration);
+            }
+
+            context.getGCListener().info("compaction cycle 0 completed in {}. Compacted {} to {}",
+                watch, head.getRecordId(), compacted.getRecordId());
+
+            int cycles = 0;
+            boolean success = false;
+            SegmentNodeState previousHead = head;
+            while (cycles < context.getGCOptions().getRetryCount() &&
+                !(success = context.getRevisions().setHead(previousHead.getRecordId(), compacted.getRecordId(), EXPEDITE_OPTION))) {
+                // Some other concurrent changes have been made.
+                // Rebase (and compact) those changes on top of the
+                // compacted state before retrying to set the head.
+                cycles++;
+                context.getGCListener().info("compaction detected concurrent commits while compacting. " +
+                        "Compacting these commits. Cycle {} of {}",
+                    cycles, context.getGCOptions().getRetryCount());
+                context.getGCListener().updateStatus(COMPACTION_RETRY.message() + cycles);
+                PrintableStopwatch cycleWatch = PrintableStopwatch.createStarted();
+
+                head = getHead(context);
+                compacted = compactor.compact(previousHead, head, compacted);
+                if (compacted == null) {
+                    context.getGCListener().warn("compaction cancelled: {}.", context.getCanceller());
+                    return compactionAborted(context, nextGeneration);
+                }
+
+                context.getGCListener().info("compaction cycle {} completed in {}. Compacted {} against {} to {}",
+                    cycles, cycleWatch, head.getRecordId(), previousHead.getRecordId(), compacted.getRecordId());
+                previousHead = head;
+            }
+
+            if (!success) {
+                context.getGCListener().info("compaction gave up compacting concurrent commits after {} cycles.", cycles);
+                int forceTimeout = context.getGCOptions().getForceTimeout();
+                if (forceTimeout > 0) {
+                    context.getGCListener().info("trying to force compact remaining commits for {} seconds. " +
+                            "Concurrent commits to the store will be blocked.",
+                        forceTimeout);
+                    context.getGCListener().updateStatus(COMPACTION_FORCE_COMPACT.message());
+                    PrintableStopwatch forceWatch = PrintableStopwatch.createStarted();
+
+                    cycles++;
+                    context.getCanceller().timeOutAfter(forceTimeout, SECONDS);
+                    compacted = forceCompact(context, previousHead, compacted, compactor);
+                    success = compacted != null;
+                    if (success) {
+                        context.getGCListener().info("compaction succeeded to force compact remaining commits after {}.", forceWatch);
+                    } else {
+                        if (context.getCanceller().get()) {
+                            context.getGCListener().warn("compaction failed to force compact remaining commits " +
+                                    "after {}. Compaction was cancelled: {}.",
+                                forceWatch, context.getCanceller());
+                        } else {
+                            context.getGCListener().warn("compaction failed to force compact remaining commits. " +
+                                    "after {}. Could not acquire exclusive access to the node store.",
+                                forceWatch);
+                        }
+                    }
+                }
+            }
+
+            if (success) {
+                // Update type of the last compaction before calling methods that could throw an exception.
+                context.getSuccessfulCompactionListener().onSuccessfulCompaction(getCompactionType());
+                writer.flush();
+                context.getFlusher().flush();
+                context.getGCListener().info("compaction succeeded in {}, after {} cycles", watch, cycles);
+                return compactionSucceeded(context, nextGeneration, compacted.getRecordId());
+            } else {
+                context.getGCListener().info("compaction failed after {}, and {} cycles", watch, cycles);
+                return compactionAborted(context, nextGeneration);
+            }
+        } catch (InterruptedException e) {
+            context.getGCListener().error("compaction interrupted", e);
+            currentThread().interrupt();
+            return compactionAborted(context, nextGeneration);
+        } catch (IOException e) {
+            context.getGCListener().error("compaction encountered an error", e);
+            return compactionAborted(context, nextGeneration);
+        }
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractCompactionStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionResult.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionResult.java?rev=1829894&r1=1829893&r2=1829894&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionResult.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionResult.java Mon Apr 23 15:15:46 2018
@@ -138,9 +138,29 @@ abstract class CompactionResult {
         };
     }
 
+    static CompactionResult notApplicable(int count) {
+        return new CompactionResult(GCGeneration.NULL, count) {
+
+            @Override
+            Predicate<GCGeneration> reclaimer() {
+                return generation -> false;
+            }
+
+            @Override
+            boolean isSuccess() {
+                return false;
+            }
+
+            @Override
+            boolean isNotApplicable() {
+                return true;
+            }
+
+        };
+    }
+
     /**
-     * @return a predicate determining which segments to {@link
-     * FileStore.GarbageCollector#cleanup(CompactionResult) clean up} for the
+     * @return a predicate determining which segments to {clean up} for the
      * given compaction result.
      */
     abstract Predicate<GCGeneration> reclaimer();
@@ -158,6 +178,10 @@ abstract class CompactionResult {
         return RecordId.NULL;
     }
 
+    boolean isNotApplicable() {
+        return false;
+    }
+
     /**
      * @return a diagnostic message describing the outcome of this compaction.
      */

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.oak.segment.Revisions;
+import org.apache.jackrabbit.oak.segment.SegmentReader;
+import org.apache.jackrabbit.oak.segment.SegmentTracker;
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
+import org.apache.jackrabbit.oak.segment.file.tar.TarFiles;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+
+interface CompactionStrategy {
+
+    interface Context {
+
+        GCListener getGCListener();
+
+        GCJournal getGCJournal();
+
+        SegmentGCOptions getGCOptions();
+
+        GCNodeWriteMonitor getCompactionMonitor();
+
+        SegmentReader getSegmentReader();
+
+        SegmentWriterFactory getSegmentWriterFactory();
+
+        Revisions getRevisions();
+
+        TarFiles getTarFiles();
+
+        BlobStore getBlobStore();
+
+        CancelCompactionSupplier getCanceller();
+
+        int getGCCount();
+
+        SuccessfulCompactionListener getSuccessfulCompactionListener();
+
+        Flusher getFlusher();
+
+        SegmentTracker getSegmentTracker();
+
+    }
+
+    CompactionResult compact(Context context);
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/CompactionStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/DefaultGarbageCollectionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/DefaultGarbageCollectionStrategy.java?rev=1829894&r1=1829893&r2=1829894&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/DefaultGarbageCollectionStrategy.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/DefaultGarbageCollectionStrategy.java Mon Apr 23 15:15:46 2018
@@ -20,21 +20,11 @@
 package org.apache.jackrabbit.oak.segment.file;
 
 import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.Thread.currentThread;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 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;
-import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCStatus.COMPACTION_RETRY;
 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.TarRevisions.EXPEDITE_OPTION;
-import static org.apache.jackrabbit.oak.segment.file.TarRevisions.timeout;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -44,45 +34,26 @@ import java.util.UUID;
 
 import javax.annotation.Nonnull;
 
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
-import org.apache.jackrabbit.oak.segment.CheckpointCompactor;
-import org.apache.jackrabbit.oak.segment.RecordId;
+import org.apache.jackrabbit.oak.segment.Revisions;
 import org.apache.jackrabbit.oak.segment.SegmentId;
-import org.apache.jackrabbit.oak.segment.SegmentNodeState;
-import org.apache.jackrabbit.oak.segment.SegmentNotFoundException;
-import org.apache.jackrabbit.oak.segment.SegmentWriter;
+import org.apache.jackrabbit.oak.segment.SegmentReader;
+import org.apache.jackrabbit.oak.segment.SegmentTracker;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
 import org.apache.jackrabbit.oak.segment.file.tar.CleanupContext;
 import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
 import org.apache.jackrabbit.oak.segment.file.tar.TarFiles;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 
 class DefaultGarbageCollectionStrategy implements GarbageCollectionStrategy {
 
-    private GCGeneration getGcGeneration(Context context) {
-        return context.getRevisions().getHead().getSegmentId().getGcGeneration();
-    }
+    private final CompactionStrategy fullCompactionStrategy = new FullCompactionStrategy();
 
-    private SegmentNodeState getBase(Context context) {
-        String root = context.getGCJournal().read().getRoot();
-        RecordId rootId = RecordId.fromString(context.getSegmentTracker(), root);
-        if (RecordId.NULL.equals(rootId)) {
-            return null;
-        }
-        try {
-            SegmentNodeState node = context.getSegmentReader().readNode(rootId);
-            node.getPropertyCount();  // Resilience: fail early with a SNFE if the segment is not there
-            return node;
-        } catch (SegmentNotFoundException snfe) {
-            context.getGCListener().error("base state " + rootId + " is not accessible", snfe);
-            return null;
-        }
-    }
+    private final CompactionStrategy tailCompactionStrategy = new FallbackCompactionStrategy(new TailCompactionStrategy(), fullCompactionStrategy);
 
-    private SegmentNodeState getHead(Context context) {
-        return context.getSegmentReader().readHeadState(context.getRevisions());
+    private GCGeneration getGcGeneration(Context context) {
+        return context.getRevisions().getHead().getSegmentId().getGcGeneration();
     }
 
     @Override
@@ -169,181 +140,90 @@ class DefaultGarbageCollectionStrategy i
         }
     }
 
-    @Override
-    public synchronized CompactionResult compactFull(Context context) {
-        context.getGCListener().info("running full compaction");
-        return compact(context, FULL, EMPTY_NODE, getGcGeneration(context).nextFull());
-    }
+    private static CompactionStrategy.Context newCompactionStrategyContext(Context context) {
+        return new CompactionStrategy.Context() {
 
-    @Override
-    public synchronized CompactionResult compactTail(Context context) {
-        context.getGCListener().info("running tail compaction");
-        SegmentNodeState base = getBase(context);
-        if (base != null) {
-            return compact(context, TAIL, base, getGcGeneration(context).nextTail());
-        }
-        context.getGCListener().info("no base state available, running full compaction instead");
-        return compact(context, FULL, EMPTY_NODE, getGcGeneration(context).nextFull());
-    }
+            @Override
+            public SegmentTracker getSegmentTracker() {
+                return context.getSegmentTracker();
+            }
 
-    private CompactionResult compact(Context context, SegmentGCOptions.GCType gcType, NodeState base, GCGeneration newGeneration) {
-        try {
-            PrintableStopwatch watch = PrintableStopwatch.createStarted();
-            context.getGCListener().info(
-                "compaction started, gc options={}, current generation={}, new generation={}",
-                context.getGCOptions(),
-                getHead(context).getRecordId().getSegment().getGcGeneration(),
-                newGeneration
-            );
-            context.getGCListener().updateStatus(COMPACTION.message());
-
-            GCJournal.GCJournalEntry gcEntry = context.getGCJournal().read();
-            long initialSize = size(context);
-
-            SegmentWriter writer = context.getSegmentWriterFactory().newSegmentWriter(newGeneration);
-
-            context.getCompactionMonitor().init(gcEntry.getRepoSize(), gcEntry.getNodes(), initialSize);
-
-            CheckpointCompactor compactor = new CheckpointCompactor(
-                context.getGCListener(),
-                context.getSegmentReader(),
-                writer,
-                context.getBlobStore(),
-                context.getCanceller(),
-                context.getCompactionMonitor()
-            );
-
-            SegmentNodeState head = getHead(context);
-            SegmentNodeState compacted = compactor.compact(base, head, base);
-            if (compacted == null) {
-                context.getGCListener().warn("compaction cancelled: {}.", context.getCanceller());
-                return compactionAborted(context, newGeneration);
-            }
-
-            context.getGCListener().info("compaction cycle 0 completed in {}. Compacted {} to {}",
-                watch, head.getRecordId(), compacted.getRecordId());
-
-            int cycles = 0;
-            boolean success = false;
-            SegmentNodeState previousHead = head;
-            while (cycles < context.getGCOptions().getRetryCount() &&
-                !(success = context.getRevisions().setHead(previousHead.getRecordId(), compacted.getRecordId(), EXPEDITE_OPTION))) {
-                // Some other concurrent changes have been made.
-                // Rebase (and compact) those changes on top of the
-                // compacted state before retrying to set the head.
-                cycles++;
-                context.getGCListener().info("compaction detected concurrent commits while compacting. " +
-                        "Compacting these commits. Cycle {} of {}",
-                    cycles, context.getGCOptions().getRetryCount());
-                context.getGCListener().updateStatus(COMPACTION_RETRY.message() + cycles);
-                PrintableStopwatch cycleWatch = PrintableStopwatch.createStarted();
-
-                head = getHead(context);
-                compacted = compactor.compact(previousHead, head, compacted);
-                if (compacted == null) {
-                    context.getGCListener().warn("compaction cancelled: {}.", context.getCanceller());
-                    return compactionAborted(context, newGeneration);
-                }
+            @Override
+            public GCListener getGCListener() {
+                return context.getGCListener();
+            }
 
-                context.getGCListener().info("compaction cycle {} completed in {}. Compacted {} against {} to {}",
-                    cycles, cycleWatch, head.getRecordId(), previousHead.getRecordId(), compacted.getRecordId());
-                previousHead = head;
-            }
-
-            if (!success) {
-                context.getGCListener().info("compaction gave up compacting concurrent commits after {} cycles.", cycles);
-                int forceTimeout = context.getGCOptions().getForceTimeout();
-                if (forceTimeout > 0) {
-                    context.getGCListener().info("trying to force compact remaining commits for {} seconds. " +
-                            "Concurrent commits to the store will be blocked.",
-                        forceTimeout);
-                    context.getGCListener().updateStatus(COMPACTION_FORCE_COMPACT.message());
-                    PrintableStopwatch forceWatch = PrintableStopwatch.createStarted();
-
-                    cycles++;
-                    context.getCanceller().timeOutAfter(forceTimeout, SECONDS);
-                    compacted = forceCompact(context, previousHead, compacted, compactor);
-                    success = compacted != null;
-                    if (success) {
-                        context.getGCListener().info("compaction succeeded to force compact remaining commits after {}.", forceWatch);
-                    } else {
-                        if (context.getCanceller().get()) {
-                            context.getGCListener().warn("compaction failed to force compact remaining commits " +
-                                    "after {}. Compaction was cancelled: {}.",
-                                forceWatch, context.getCanceller());
-                        } else {
-                            context.getGCListener().warn("compaction failed to force compact remaining commits. " +
-                                    "after {}. Could not acquire exclusive access to the node store.",
-                                forceWatch);
-                        }
-                    }
-                }
+            @Override
+            public GCJournal getGCJournal() {
+                return context.getGCJournal();
             }
 
-            if (success) {
-                // Update type of the last compaction before calling methods that could throw an exception.
-                context.getSuccessfulCompactionListener().onSuccessfulCompaction(gcType);
-                writer.flush();
-                context.getFlusher().flush();
-                context.getGCListener().info("compaction succeeded in {}, after {} cycles", watch, cycles);
-                return compactionSucceeded(context, gcType, newGeneration, compacted.getRecordId());
-            } else {
-                context.getGCListener().info("compaction failed after {}, and {} cycles", watch, cycles);
-                return compactionAborted(context, newGeneration);
+            @Override
+            public SegmentGCOptions getGCOptions() {
+                return context.getGCOptions();
             }
-        } catch (InterruptedException e) {
-            context.getGCListener().error("compaction interrupted", e);
-            currentThread().interrupt();
-            return compactionAborted(context, newGeneration);
-        } catch (IOException e) {
-            context.getGCListener().error("compaction encountered an error", e);
-            return compactionAborted(context, newGeneration);
-        }
-    }
 
-    private CompactionResult compactionAborted(Context context, GCGeneration generation) {
-        context.getGCListener().compactionFailed(generation);
-        return CompactionResult.aborted(getGcGeneration(context), generation, context.getGCCount());
-    }
+            @Override
+            public GCNodeWriteMonitor getCompactionMonitor() {
+                return context.getCompactionMonitor();
+            }
+
+            @Override
+            public SegmentReader getSegmentReader() {
+                return context.getSegmentReader();
+            }
+
+            @Override
+            public SegmentWriterFactory getSegmentWriterFactory() {
+                return context.getSegmentWriterFactory();
+            }
 
-    private CompactionResult compactionSucceeded(
-        Context context,
-        SegmentGCOptions.GCType gcType,
-        GCGeneration generation,
-        RecordId compactedRootId
-    ) {
-        context.getGCListener().compactionSucceeded(generation);
-        return CompactionResult.succeeded(gcType, generation, context.getGCOptions(), compactedRootId, context.getGCCount());
+            @Override
+            public Revisions getRevisions() {
+                return context.getRevisions();
+            }
+
+            @Override
+            public TarFiles getTarFiles() {
+                return context.getTarFiles();
+            }
+
+            @Override
+            public BlobStore getBlobStore() {
+                return context.getBlobStore();
+            }
+
+            @Override
+            public CancelCompactionSupplier getCanceller() {
+                return context.getCanceller();
+            }
+
+            @Override
+            public int getGCCount() {
+                return context.getGCCount();
+            }
+
+            @Override
+            public SuccessfulCompactionListener getSuccessfulCompactionListener() {
+                return context.getSuccessfulCompactionListener();
+            }
+
+            @Override
+            public Flusher getFlusher() {
+                return context.getFlusher();
+            }
+
+        };
     }
 
-    private SegmentNodeState forceCompact(
-        Context context,
-        final NodeState base,
-        final NodeState onto,
-        final CheckpointCompactor compactor
-    ) throws InterruptedException {
-        RecordId compactedId = setHead(context, headId -> {
-            try {
-                PrintableStopwatch t = PrintableStopwatch.createStarted();
-                SegmentNodeState after = compactor.compact(base, context.getSegmentReader().readNode(headId), onto);
-                if (after != null) {
-                    return after.getRecordId();
-                }
-                context.getGCListener().info("compaction cancelled after {}", t);
-                return null;
-            } catch (IOException e) {
-                context.getGCListener().error("error during forced compaction.", e);
-                return null;
-            }
-        });
-        if (compactedId == null) {
-            return null;
-        }
-        return context.getSegmentReader().readNode(compactedId);
+    @Override
+    public synchronized CompactionResult compactFull(Context context) {
+        return fullCompactionStrategy.compact(newCompactionStrategyContext(context));
     }
 
-    private RecordId setHead(Context context, Function<RecordId, RecordId> f) throws InterruptedException {
-        return context.getRevisions().setHead(f, timeout(context.getGCOptions().getForceTimeout(), SECONDS));
+    @Override
+    public synchronized CompactionResult compactTail(Context context) {
+        return tailCompactionStrategy.compact(newCompactionStrategyContext(context));
     }
 
     private GCEstimationResult estimateCompactionGain(Context context, boolean full) {

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+class FallbackCompactionStrategy implements CompactionStrategy {
+
+    private final CompactionStrategy primary;
+
+    private final CompactionStrategy fallback;
+
+    FallbackCompactionStrategy(CompactionStrategy primary, CompactionStrategy fallback) {
+        this.primary = primary;
+        this.fallback = fallback;
+    }
+
+    @Override
+    public CompactionResult compact(Context context) {
+        CompactionResult result = primary.compact(context);
+
+        if (result.isNotApplicable()) {
+            return fallback.compact(context);
+        }
+
+        return result;
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FallbackCompactionStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.FULL;
+
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
+import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
+
+class FullCompactionStrategy extends AbstractCompactionStrategy {
+
+    @Override
+    GCType getCompactionType() {
+        return FULL;
+    }
+
+    @Override
+    GCGeneration nextGeneration(GCGeneration current) {
+        return current.nextFull();
+    }
+
+    @Override
+    public CompactionResult compact(Context context) {
+        return compact(context, EMPTY_NODE);
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FullCompactionStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollectionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollectionStrategy.java?rev=1829894&r1=1829893&r2=1829894&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollectionStrategy.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollectionStrategy.java Mon Apr 23 15:15:46 2018
@@ -27,9 +27,7 @@ import org.apache.jackrabbit.oak.segment
 import org.apache.jackrabbit.oak.segment.SegmentCache;
 import org.apache.jackrabbit.oak.segment.SegmentReader;
 import org.apache.jackrabbit.oak.segment.SegmentTracker;
-import org.apache.jackrabbit.oak.segment.SegmentWriter;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
-import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
 import org.apache.jackrabbit.oak.segment.file.tar.TarFiles;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 
@@ -41,18 +39,6 @@ interface GarbageCollectionStrategy {
 
     }
 
-    interface SuccessfulCompactionListener {
-
-        void onSuccessfulCompaction(SegmentGCOptions.GCType type);
-
-    }
-
-    interface SegmentWriterFactory {
-
-        SegmentWriter newSegmentWriter(GCGeneration generation);
-
-    }
-
     interface Context {
 
         SegmentGCOptions getGCOptions();

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollector.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollector.java?rev=1829894&r1=1829893&r2=1829894&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollector.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/GarbageCollector.java Mon Apr 23 15:15:46 2018
@@ -38,7 +38,6 @@ import org.apache.jackrabbit.oak.segment
 import org.apache.jackrabbit.oak.segment.SegmentTracker;
 import org.apache.jackrabbit.oak.segment.SegmentWriter;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
-import org.apache.jackrabbit.oak.segment.file.GarbageCollectionStrategy.SuccessfulCompactionListener;
 import org.apache.jackrabbit.oak.segment.file.GarbageCollectionStrategy.SuccessfulGarbageCollectionListener;
 import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
 import org.apache.jackrabbit.oak.segment.file.tar.TarFiles;
@@ -95,7 +94,7 @@ class GarbageCollector {
 
     private final Flusher flusher;
 
-    private final GarbageCollectionStrategy.SegmentWriterFactory segmentWriterFactory;
+    private final SegmentWriterFactory segmentWriterFactory;
 
     private final GCNodeWriteMonitor compactionMonitor;
 
@@ -129,7 +128,7 @@ class GarbageCollector {
         FileStoreStats stats,
         CancelCompactionSupplier cancel,
         Flusher flusher,
-        GarbageCollectionStrategy.SegmentWriterFactory segmentWriterFactory
+        SegmentWriterFactory segmentWriterFactory
     ) {
         this.gcOptions = gcOptions;
         this.gcListener = new PrefixedGCListener(gcListener, GC_COUNT);
@@ -187,7 +186,7 @@ class GarbageCollector {
             }
 
             @Override
-            public GarbageCollectionStrategy.SegmentWriterFactory getSegmentWriterFactory() {
+            public SegmentWriterFactory getSegmentWriterFactory() {
                 return segmentWriterFactory;
             }
 

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.oak.segment.SegmentWriter;
+import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
+
+interface SegmentWriterFactory {
+
+    SegmentWriter newSegmentWriter(GCGeneration generation);
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SegmentWriterFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
+
+interface SuccessfulCompactionListener {
+
+    void onSuccessfulCompaction(SegmentGCOptions.GCType type);
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/SuccessfulCompactionListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java?rev=1829894&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java Mon Apr 23 15:15:46 2018
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType.TAIL;
+
+import org.apache.jackrabbit.oak.segment.RecordId;
+import org.apache.jackrabbit.oak.segment.SegmentNotFoundException;
+import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
+import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+class TailCompactionStrategy extends AbstractCompactionStrategy {
+
+    @Override
+    GCType getCompactionType() {
+        return TAIL;
+    }
+
+    @Override
+    GCGeneration nextGeneration(GCGeneration current) {
+        return current.nextTail();
+    }
+
+    @Override
+    public CompactionResult compact(Context context) {
+        NodeState base = getBase(context);
+
+        if (base == null) {
+            context.getGCListener().info("no base state available, tail compaction is not applicable");
+            return CompactionResult.notApplicable(context.getGCCount());
+        }
+
+        return compact(context, base);
+    }
+
+    private static NodeState getBase(Context context) {
+        RecordId id = getLastCompactedRootId(context);
+
+        if (RecordId.NULL.equals(id)) {
+            return null;
+        }
+
+        // Nodes are read lazily. In order to force a read operation for the requested
+        // node, the property count is computed. Computing the property count requires
+        // access to the node template, whose ID is stored in the content of the node.
+        // Accessing the content of the node forces a read operation for the segment
+        // containing the node. If the following code completes without throwing a
+        // SNFE, we can be sure that *at least* the root node can be accessed. This
+        // doesn't say anything about the health of the full closure of the head
+        // state.
+
+        try {
+            NodeState node = getLastCompactedRootNode(context);
+            node.getPropertyCount();
+            return node;
+        } catch (SegmentNotFoundException e) {
+            context.getGCListener().error("base state " + id + " is not accessible", e);
+            return null;
+        }
+    }
+
+    private static String getLastCompactedRoot(Context context) {
+        return context.getGCJournal().read().getRoot();
+    }
+
+    private static RecordId getLastCompactedRootId(Context context) {
+        return RecordId.fromString(context.getSegmentTracker(), getLastCompactedRoot(context));
+    }
+
+    private static NodeState getLastCompactedRootNode(Context context) {
+        return context.getSegmentReader().readNode(getLastCompactedRootId(context));
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/TailCompactionStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native