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 2015/10/06 17:32:24 UTC

svn commit: r1707076 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/segment/ main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/ main/java/org/apache/jackrabbit/oak/plugins/segment/file/ test/java/org/a...

Author: frm
Date: Tue Oct  6 15:32:24 2015
New Revision: 1707076

URL: http://svn.apache.org/viewvc?rev=1707076&view=rev
Log:
OAK-2879 - Compaction should cancel itself when there is not enough available disk space

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/CompactionStrategy.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactorTest.java

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java?rev=1707076&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java Tue Oct  6 15:32:24 2015
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.segment;
+
+import com.google.common.base.Supplier;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+/**
+ * A {@code NodeStateDiff} that cancels itself when a condition occurs. The
+ * condition is represented by an externally provided instance of {@code
+ * Supplier}. If the {@code Supplier} returns {@code true}, the diffing process
+ * will be canceled at the first possible occasion.
+ */
+class CancelableDiff implements NodeStateDiff {
+
+    private final NodeStateDiff delegate;
+
+    private final Supplier<Boolean> canceled;
+
+    public CancelableDiff(NodeStateDiff delegate, Supplier<Boolean> canceled) {
+        this.delegate = delegate;
+        this.canceled = canceled;
+    }
+
+    @Override
+    public final boolean propertyAdded(PropertyState after) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.propertyAdded(after);
+    }
+
+    @Override
+    public final boolean propertyChanged(PropertyState before, PropertyState after) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.propertyChanged(before, after);
+    }
+
+    @Override
+    public final boolean propertyDeleted(PropertyState before) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.propertyDeleted(before);
+    }
+
+    @Override
+    public final boolean childNodeAdded(String name, NodeState after) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.childNodeAdded(name, after);
+    }
+
+    @Override
+    public final boolean childNodeChanged(String name, NodeState before, NodeState after) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.childNodeChanged(name, before, after);
+    }
+
+    @Override
+    public final boolean childNodeDeleted(String name, NodeState before) {
+        if (canceled.get()) {
+            return false;
+        }
+
+        return delegate.childNodeDeleted(name, before);
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java?rev=1707076&r1=1707075&r2=1707076&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java Tue Oct  6 15:32:24 2015
@@ -16,20 +16,10 @@
  */
 package org.apache.jackrabbit.oak.plugins.segment;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Maps.newHashMap;
-import static org.apache.jackrabbit.oak.api.Type.BINARIES;
-import static org.apache.jackrabbit.oak.api.Type.BINARY;
-import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.hash.Hashing;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.PropertyState;
@@ -44,9 +34,22 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.oak.api.Type.BINARIES;
+import static org.apache.jackrabbit.oak.api.Type.BINARY;
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+
 /**
  * Tool for compacting segments.
  */
@@ -88,13 +91,30 @@ public class Compactor {
      */
     private final boolean cloneBinaries;
 
+    /**
+     * Allows the cancellation of the compaction process. If this {@code
+     * Supplier} returns {@code true}, this compactor will cancel compaction and
+     * return a partial {@code SegmentNodeState} containing the changes
+     * compacted before the cancellation.
+     */
+    private final Supplier<Boolean> cancel;
+
     public Compactor(SegmentWriter writer) {
+        this(writer, Suppliers.ofInstance(false));
+    }
+
+    public Compactor(SegmentWriter writer, Supplier<Boolean> cancel) {
         this.writer = writer;
         this.map = new InMemoryCompactionMap(writer.getTracker());
         this.cloneBinaries = false;
+        this.cancel = cancel;
     }
 
     public Compactor(FileStore store, CompactionStrategy compactionStrategy) {
+        this(store, compactionStrategy, Suppliers.ofInstance(false));
+    }
+
+    public Compactor(FileStore store, CompactionStrategy compactionStrategy, Supplier<Boolean> cancel) {
         this.writer = store.createSegmentWriter();
         if (compactionStrategy.getPersistCompactionMap()) {
             this.map = new PersistedCompactionMap(store);
@@ -105,11 +125,12 @@ public class Compactor {
         if (compactionStrategy.isOfflineCompaction()) {
             includeInMap = new OfflineCompactionPredicate();
         }
+        this.cancel = cancel;
     }
 
     protected SegmentNodeBuilder process(NodeState before, NodeState after, NodeState onto) {
         SegmentNodeBuilder builder = new SegmentNodeBuilder(writer.writeNode(onto), writer);
-        after.compareAgainstBaseState(before, new CompactDiff(builder));
+        after.compareAgainstBaseState(before, newCompactionDiff(builder));
         return builder;
     }
 
@@ -215,7 +236,7 @@ public class Compactor {
 
             NodeBuilder child = EmptyNodeState.EMPTY_NODE.builder();
             boolean success = EmptyNodeState.compareAgainstEmptyState(after,
-                    new CompactDiff(child, path, name));
+                    newCompactionDiff(child, path, name));
 
             if (success) {
                 SegmentNodeState state = writer.writeNode(child.getNodeState());
@@ -248,7 +269,7 @@ public class Compactor {
 
             NodeBuilder child = builder.getChildNode(name);
             boolean success = after.compareAgainstBaseState(before,
-                    new CompactDiff(child, path, name));
+                    newCompactionDiff(child, path, name));
 
             if (success) {
                 RecordId compactedId = writer.writeNode(child.getNodeState())
@@ -263,6 +284,14 @@ public class Compactor {
 
     }
 
+    private NodeStateDiff newCompactionDiff(NodeBuilder builder) {
+        return new CancelableDiff(new CompactDiff(builder), cancel);
+    }
+
+    private NodeStateDiff newCompactionDiff(NodeBuilder child, String path, String name) {
+        return new CancelableDiff(new CompactDiff(child, path, name), cancel);
+    }
+
     private PropertyState compact(PropertyState property) {
         String name = property.getName();
         Type<?> type = property.getType();

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/CompactionStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/CompactionStrategy.java?rev=1707076&r1=1707075&r2=1707076&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/CompactionStrategy.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/compaction/CompactionStrategy.java Tue Oct  6 15:32:24 2015
@@ -18,15 +18,14 @@
  */
 package org.apache.jackrabbit.oak.plugins.segment.compaction;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.System.currentTimeMillis;
-
-import java.util.concurrent.Callable;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentId;
 
 import javax.annotation.Nonnull;
+import java.util.concurrent.Callable;
 
-import org.apache.jackrabbit.oak.plugins.segment.SegmentId;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.System.currentTimeMillis;
 
 public abstract class CompactionStrategy {
 
@@ -293,4 +292,18 @@ public abstract class CompactionStrategy
         this.offlineCompaction = offlineCompaction;
     }
 
+    /**
+     * Check if the approximate repository size is getting too big compared with
+     * the available space on disk.
+     *
+     * @param repositoryDiskSpace Approximate size of the disk space occupied by
+     *                            the repository.
+     * @param availableDiskSpace  Currently available disk space.
+     * @return {@code true} if the available disk space is considered enough for
+     * normal repository operations.
+     */
+    public boolean isDiskSpaceSufficient(long repositoryDiskSpace, long availableDiskSpace) {
+        return availableDiskSpace > 0.25 * repositoryDiskSpace;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java?rev=1707076&r1=1707075&r2=1707076&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java Tue Oct  6 15:32:24 2015
@@ -16,46 +16,8 @@
  */
 package org.apache.jackrabbit.oak.plugins.segment.file;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Lists.newArrayListWithCapacity;
-import static com.google.common.collect.Lists.newLinkedList;
-import static com.google.common.collect.Maps.newHashMap;
-import static com.google.common.collect.Maps.newLinkedHashMap;
-import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.String.format;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.singletonMap;
-import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static org.apache.jackrabbit.oak.plugins.segment.CompactionMap.sum;
-import static org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.NO_COMPACTION;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileLock;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.annotation.Nonnull;
-
 import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
 import org.apache.jackrabbit.oak.plugins.segment.CompactionMap;
@@ -79,6 +41,46 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileLock;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.newArrayListWithCapacity;
+import static com.google.common.collect.Lists.newLinkedList;
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Maps.newLinkedHashMap;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.format;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.plugins.segment.CompactionMap.sum;
+import static org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.NO_COMPACTION;
+
 /**
  * The storage implementation for tar files.
  */
@@ -148,6 +150,14 @@ public class FileStore implements Segmen
      */
     private final BackgroundThread compactionThread;
 
+    /**
+     * This background thread periodically asks the {@code CompactionStrategy}
+     * to compare the approximate size of the repository with the available disk
+     * space. The result of this comparison is stored in the state of this
+     * {@code FileStore}.
+     */
+    private final BackgroundThread diskSpaceThread;
+
     private CompactionStrategy compactionStrategy = NO_COMPACTION;
 
     /**
@@ -173,6 +183,17 @@ public class FileStore implements Segmen
     private final GCMonitor gcMonitor;
 
     /**
+     * Represents the approximate size on disk of the repository.
+     */
+    private final AtomicLong approximateSize;
+
+    /**
+     * This flag is periodically updated by calling the {@code
+     * CompactionStrategy} at regular intervals.
+     */
+    private final AtomicBoolean sufficientDiskSpace;
+
+    /**
      * Create a new instance of a {@link Builder} for a file store.
      * @param directory  directory where the tar files are stored
      * @return a new {@link Builder} instance.
@@ -456,11 +477,26 @@ public class FileStore implements Segmen
                             maybeCompact(true);
                         }
                     });
+
+            diskSpaceThread = new BackgroundThread("TarMK disk space check [" + directory + "]", TimeUnit.MINUTES.toMillis(1), new Runnable() {
+
+                @Override
+                public void run() {
+                    checkDiskSpace();
+                }
+
+            });
+
+            approximateSize = new AtomicLong(size());
         } else {
             this.flushThread = null;
             this.compactionThread = null;
+            diskSpaceThread = null;
+            approximateSize = null;
         }
 
+        sufficientDiskSpace = new AtomicBoolean(true);
+
         if (readonly) {
             log.info("TarMK ReadOnly opened: {} (mmap={})", directory,
                     memoryMapping);
@@ -773,6 +809,7 @@ public class FileStore implements Segmen
 
         cm.remove(cleanedIds);
         long finalSize = size();
+        approximateSize.set(finalSize);
         gcMonitor.cleaned(initialSize - finalSize, finalSize);
         gcMonitor.info("TarMK revision cleanup completed in {}. Post cleanup size is {} " +
                 "and space reclaimed {}. Compaction map weight/depth is {}/{}.", watch,
@@ -791,6 +828,36 @@ public class FileStore implements Segmen
     }
 
     /**
+     * Returns the cancellation policy for the compaction phase. If the disk
+     * space was considered insufficient at least once during compaction (or if
+     * the space was never sufficient to begin with), compaction is considered
+     * canceled.
+     *
+     * @return a flag indicating if compaction should be canceled.
+     */
+    private Supplier<Boolean> newCancelCompactionCondition() {
+        return new Supplier<Boolean>() {
+
+            private boolean canceled = false;
+
+            @Override
+            public Boolean get() {
+
+                // The canceled flag can only transition from false (its initial
+                // value), to true. Once compaction is considered canceled,
+                // there should be no way to go back.
+
+                if (!sufficientDiskSpace.get()) {
+                    canceled = true;
+                }
+
+                return canceled;
+            }
+
+        };
+    }
+
+    /**
      * Copy every referenced record in data (non-bulk) segments. Bulk segments
      * are fully kept (they are only removed in cleanup, if there is no
      * reference to them).
@@ -799,9 +866,9 @@ public class FileStore implements Segmen
         checkArgument(!compactionStrategy.equals(NO_COMPACTION),
                 "You must set a compactionStrategy before calling compact");
         gcMonitor.info("TarMK compaction running, strategy={}", compactionStrategy);
-
         long start = System.currentTimeMillis();
-        Compactor compactor = new Compactor(this, compactionStrategy);
+        Supplier<Boolean> compactionCanceled = newCancelCompactionCondition();
+        Compactor compactor = new Compactor(this, compactionStrategy, compactionCanceled);
         SegmentNodeState before = getHead();
         long existing = before.getChildNode(SegmentNodeStore.CHECKPOINTS)
                 .getChildNodeCount(Long.MAX_VALUE);
@@ -813,6 +880,11 @@ public class FileStore implements Segmen
 
         SegmentNodeState after = compactor.compact(EMPTY_NODE, before, EMPTY_NODE);
 
+        if (compactionCanceled.get()) {
+            gcMonitor.warn("TarMK compaction was canceled, not enough disk space available.");
+            return;
+        }
+
         Callable<Boolean> setHead = new SetHead(before, after, compactor);
         try {
             int cycles = 0;
@@ -826,6 +898,12 @@ public class FileStore implements Segmen
                         "Compacting these commits. Cycle {}", cycles);
                 SegmentNodeState head = getHead();
                 after = compactor.compact(before, head, after);
+
+                if (compactionCanceled.get()) {
+                    gcMonitor.warn("TarMK compaction was canceled, not enough disk space available.");
+                    return;
+                }
+
                 before = head;
                 setHead = new SetHead(head, after, compactor);
             }
@@ -899,6 +977,7 @@ public class FileStore implements Segmen
         // threads before acquiring the synchronization lock
         closeAndLogOnFail(compactionThread);
         closeAndLogOnFail(flushThread);
+        closeAndLogOnFail(diskSpaceThread);
         synchronized (this) {
             try {
                 flush();
@@ -1035,6 +1114,7 @@ public class FileStore implements Segmen
             if (size >= maxFileSize) {
                 newWriter();
             }
+            approximateSize.addAndGet(TarWriter.BLOCK_SIZE + length + TarWriter.getPaddingSize(length));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -1117,6 +1197,25 @@ public class FileStore implements Segmen
         persistedHead.set(id);
     }
 
+    private void checkDiskSpace() {
+        long repositoryDiskSpace = approximateSize.get();
+        long availableDiskSpace = directory.getFreeSpace();
+        boolean updated = compactionStrategy.isDiskSpaceSufficient(repositoryDiskSpace, availableDiskSpace);
+        boolean previous = sufficientDiskSpace.getAndSet(updated);
+
+        if (previous && !updated) {
+            log.warn("Available disk space ({}) is too low, current repository size is approx. {}",
+                    humanReadableByteCount(availableDiskSpace),
+                    humanReadableByteCount(repositoryDiskSpace));
+        }
+
+        if (updated && !previous) {
+            log.info("Available disk space ({}) is sufficient again for repository operations, current repository size is approx. {}",
+                    humanReadableByteCount(availableDiskSpace),
+                    humanReadableByteCount(repositoryDiskSpace));
+        }
+    }
+
     /**
      * A read only {@link FileStore} implementation that supports
      * going back to old revisions.

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java?rev=1707076&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java Tue Oct  6 15:32:24 2015
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.segment;
+
+import com.google.common.base.Suppliers;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+public class CancelableDiffTest {
+
+    @Test
+    public void testPropertyAddedInterruptible() throws Throwable {
+        PropertyState after = mock(PropertyState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).propertyAdded(after);
+
+        assertTrue(newCancelableDiff(wrapped, false).propertyAdded(after));
+        assertFalse(newCancelableDiff(wrapped, true).propertyAdded(after));
+    }
+
+    @Test
+    public void testPropertyChangedInterruptible() throws Throwable {
+        PropertyState before = mock(PropertyState.class);
+        PropertyState after = mock(PropertyState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).propertyChanged(before, after);
+
+        assertTrue(newCancelableDiff(wrapped, false).propertyChanged(before, after));
+        assertFalse(newCancelableDiff(wrapped, true).propertyChanged(before, after));
+    }
+
+    @Test
+    public void testPropertyDeletedInterruptible() throws Throwable {
+        PropertyState before = mock(PropertyState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).propertyDeleted(before);
+
+        assertTrue(newCancelableDiff(wrapped, false).propertyDeleted(before));
+        assertFalse(newCancelableDiff(wrapped, true).propertyDeleted(before));
+    }
+
+    @Test
+    public void testChildNodeAddedInterruptible() throws Throwable {
+        NodeState after = mock(NodeState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).childNodeAdded("name", after);
+
+        assertTrue(newCancelableDiff(wrapped, false).childNodeAdded("name", after));
+        assertFalse(newCancelableDiff(wrapped, true).childNodeAdded("name", after));
+    }
+
+    @Test
+    public void testChildNodeChangedInterruptible() throws Throwable {
+        NodeState before = mock(NodeState.class);
+        NodeState after = mock(NodeState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).childNodeChanged("name", before, after);
+
+        assertTrue(newCancelableDiff(wrapped, false).childNodeChanged("name", before, after));
+        assertFalse(newCancelableDiff(wrapped, true).childNodeChanged("name", before, after));
+    }
+
+    @Test
+    public void testChildNodeDeletedInterruptible() throws Throwable {
+        NodeState before = mock(NodeState.class);
+
+        NodeStateDiff wrapped = mock(NodeStateDiff.class);
+        doReturn(true).when(wrapped).childNodeDeleted("name", before);
+
+        assertTrue(newCancelableDiff(wrapped, false).childNodeDeleted("name", before));
+        assertFalse(newCancelableDiff(wrapped, true).childNodeDeleted("name", before));
+    }
+
+    private NodeStateDiff newCancelableDiff(NodeStateDiff wrapped, boolean cancel) {
+        return new CancelableDiff(wrapped, Suppliers.ofInstance(cancel));
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CancelableDiffTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactorTest.java?rev=1707076&r1=1707075&r2=1707076&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactorTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactorTest.java Tue Oct  6 15:32:24 2015
@@ -16,8 +16,9 @@
  */
 package org.apache.jackrabbit.oak.plugins.segment;
 
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import junit.framework.Assert;
-
 import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.plugins.segment.memory.MemoryStore;
@@ -27,33 +28,61 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
+import static org.junit.Assert.assertFalse;
+
 public class CompactorTest {
 
+    private SegmentStore segmentStore;
+
+    @Before
+    public void openSegmentStore() {
+        segmentStore = new MemoryStore();
+    }
+
+    @After
+    public void closeSegmentStore() {
+        segmentStore.close();
+    }
+
     @Test
     public void testCompactor() throws Exception {
-        MemoryStore source = new MemoryStore();
-        try {
-            NodeStore store = new SegmentNodeStore(source);
-            init(store);
-
-            Compactor compactor = new Compactor(source.getTracker().getWriter());
-            addTestContent(store, 0);
-
-            NodeState initial = store.getRoot();
-            SegmentNodeState after = compactor
-                    .compact(initial, store.getRoot(), initial);
-            Assert.assertEquals(store.getRoot(), after);
-
-            addTestContent(store, 1);
-            after = compactor.compact(initial, store.getRoot(), initial);
-            Assert.assertEquals(store.getRoot(), after);
-
-        } finally {
-            source.close();
-        }
+        NodeStore store = new SegmentNodeStore(segmentStore);
+        init(store);
+
+        Compactor compactor = new Compactor(segmentStore.getTracker().getWriter());
+        addTestContent(store, 0);
+
+        NodeState initial = store.getRoot();
+        SegmentNodeState after = compactor
+                .compact(initial, store.getRoot(), initial);
+        Assert.assertEquals(store.getRoot(), after);
+
+        addTestContent(store, 1);
+        after = compactor.compact(initial, store.getRoot(), initial);
+        Assert.assertEquals(store.getRoot(), after);
+    }
+
+    @Test
+    public void testCancel() throws Throwable {
+
+        // Create a Compactor that will cancel itself as soon as possible. The
+        // early cancellation is the reason why the returned SegmentNodeState
+        // doesn't have the child named "b".
+
+        NodeStore store = SegmentNodeStore.newSegmentNodeStore(segmentStore).create();
+        Compactor compactor = new Compactor(segmentStore.getTracker().getWriter(), Suppliers.ofInstance(true));
+        SegmentNodeState sns = compactor.compact(store.getRoot(), addChild(store.getRoot(), "b"), store.getRoot());
+        assertFalse(sns.hasChildNode("b"));
+    }
 
+    private NodeState addChild(NodeState current, String name) {
+        NodeBuilder builder = current.builder();
+        builder.child(name);
+        return builder.getNodeState();
     }
 
     private static void init(NodeStore store) {