Author: mduerig
Date: Thu Oct 6 09:03:56 2016
New Revision: 1763538
URL: http://svn.apache.org/viewvc?rev=1763538&view=rev
Log:
OAK-4685: Avoid concurrent calls to FileStore.cleanup() and FileStore.compact()
Throw an ISE on concurrent call of compact / cleanup
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/test/java/org/apache/jackrabbit/oak/segment/CompactionAndCleanupIT.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=1763538&r1=1763537&r2=1763538&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
Thu Oct 6 09:03:56 2016
@@ -63,6 +63,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -193,6 +194,11 @@ public class FileStore implements Segmen
private final GCJournal gcJournal;
/**
+ * Semaphore guarding overlapping calls to {@link #compact()} and {@link
#cleanup()}
+ */
+ private final Semaphore gcSemaphore = new Semaphore(1);
+
+ /**
* List of old tar file generations that are waiting to be removed. They
can
* not be removed immediately, because they first need to be closed, and
the
* JVM needs to release the memory mapped file references.
@@ -512,7 +518,10 @@ public class FileStore implements Segmen
if (!gcOptions.isPaused()) {
logAndClear(segmentWriter.getNodeWriteTimeStats(),
segmentWriter.getNodeCompactTimeStats());
log(segmentWriter.getNodeCacheOccupancyInfo());
- compact();
+ Runnable cleanupTask = compact();
+ if (cleanupTask != null) {
+ cleanupTask.run();
+ }
logAndClear(segmentWriter.getNodeWriteTimeStats(),
segmentWriter.getNodeCompactTimeStats());
log(segmentWriter.getNodeCacheOccupancyInfo());
} else {
@@ -724,6 +733,24 @@ public class FileStore implements Segmen
}
/**
+ * Try to acquire the passed {@code semaphore}
+ * @param semaphore
+ * @return a closable for releasing the {@code semaphore}
+ * @throws IllegalStateException if acquiring the {@code semaphore}
failed.
+ */
+ private static Closeable withSemaphore(@Nonnull final Semaphore semaphore)
{
+ if (!semaphore.tryAcquire()) {
+ throw new IllegalStateException("Compaction or cleanup already in
progress");
+ }
+ return new Closeable() {
+ @Override
+ public void close() {
+ semaphore.release();
+ }
+ };
+ }
+
+ /**
* Run garbage collection on the segment level: reclaim those data segments
* that are from an old segment generation and those bulk segments that
are not
* reachable anymore.
@@ -751,105 +778,107 @@ public class FileStore implements Segmen
@Nonnull Predicate<Integer> reclaimGeneration,
@Nonnull String gcInfo)
throws IOException {
- Stopwatch watch = Stopwatch.createStarted();
- Set<UUID> bulkRefs = newHashSet();
- Map<TarReader, TarReader> cleaned = newLinkedHashMap();
+ try (Closeable s = withSemaphore(gcSemaphore)) {
+ Stopwatch watch = Stopwatch.createStarted();
+ Set<UUID> bulkRefs = newHashSet();
+ Map<TarReader, TarReader> cleaned = newLinkedHashMap();
- long initialSize = 0;
- fileStoreLock.writeLock().lock();
- try {
- gcListener.info("TarMK GC #{}: cleanup started.", GC_COUNT);
+ long initialSize = 0;
+ fileStoreLock.writeLock().lock();
+ try {
+ gcListener.info("TarMK GC #{}: cleanup started.", GC_COUNT);
- newWriter();
- segmentCache.clear();
+ newWriter();
+ segmentCache.clear();
- // Suggest to the JVM that now would be a good time
- // to clear stale weak references in the SegmentTracker
- System.gc();
+ // Suggest to the JVM that now would be a good time
+ // to clear stale weak references in the SegmentTracker
+ System.gc();
- collectBulkReferences(bulkRefs);
+ collectBulkReferences(bulkRefs);
- for (TarReader reader : readers) {
- cleaned.put(reader, reader);
- initialSize += reader.size();
+ for (TarReader reader : readers) {
+ cleaned.put(reader, reader);
+ initialSize += reader.size();
+ }
+ } finally {
+ fileStoreLock.writeLock().unlock();
}
- } finally {
- fileStoreLock.writeLock().unlock();
- }
-
- gcListener.info("TarMK GC #{}: current repository size is {} ({}
bytes)",
- GC_COUNT, humanReadableByteCount(initialSize), initialSize);
-
- Set<UUID> reclaim = newHashSet();
- for (TarReader reader : cleaned.keySet()) {
- reader.mark(bulkRefs, reclaim, reclaimGeneration);
- log.info("{}: size of bulk references/reclaim set {}/{}",
- reader, bulkRefs.size(), reclaim.size());
- if (shutdown) {
- gcListener.info("TarMK GC #{}: cleanup interrupted", GC_COUNT);
- break;
- }
- }
- Set<UUID> reclaimed = newHashSet();
- for (TarReader reader : cleaned.keySet()) {
- cleaned.put(reader, reader.sweep(reclaim, reclaimed));
- if (shutdown) {
- gcListener.info("TarMK GC #{}: cleanup interrupted", GC_COUNT);
- break;
+
+ gcListener.info("TarMK GC #{}: current repository size is {} ({}
bytes)",
+ GC_COUNT, humanReadableByteCount(initialSize),
initialSize);
+
+ Set<UUID> reclaim = newHashSet();
+ for (TarReader reader : cleaned.keySet()) {
+ reader.mark(bulkRefs, reclaim, reclaimGeneration);
+ log.info("{}: size of bulk references/reclaim set {}/{}",
+ reader, bulkRefs.size(), reclaim.size());
+ if (shutdown) {
+ gcListener.info("TarMK GC #{}: cleanup interrupted",
GC_COUNT);
+ break;
+ }
+ }
+ Set<UUID> reclaimed = newHashSet();
+ for (TarReader reader : cleaned.keySet()) {
+ cleaned.put(reader, reader.sweep(reclaim, reclaimed));
+ if (shutdown) {
+ gcListener.info("TarMK GC #{}: cleanup interrupted",
GC_COUNT);
+ break;
+ }
}
- }
- // it doesn't account for concurrent commits that might have happened
- long afterCleanupSize = 0;
-
- List<TarReader> oldReaders = newArrayList();
- fileStoreLock.writeLock().lock();
- try {
- // Replace current list of reader with the cleaned readers taking
care not to lose
- // any new reader that might have come in through concurrent calls
to newWriter()
- List<TarReader> sweptReaders = newArrayList();
- for (TarReader reader : readers) {
- if (cleaned.containsKey(reader)) {
- TarReader newReader = cleaned.get(reader);
- if (newReader != null) {
- sweptReaders.add(newReader);
- afterCleanupSize += newReader.size();
- }
- // if these two differ, the former represents the swept
version of the latter
- if (newReader != reader) {
- oldReaders.add(reader);
+ // it doesn't account for concurrent commits that might have
happened
+ long afterCleanupSize = 0;
+
+ List<TarReader> oldReaders = newArrayList();
+ fileStoreLock.writeLock().lock();
+ try {
+ // Replace current list of reader with the cleaned readers
taking care not to lose
+ // any new reader that might have come in through concurrent
calls to newWriter()
+ List<TarReader> sweptReaders = newArrayList();
+ for (TarReader reader : readers) {
+ if (cleaned.containsKey(reader)) {
+ TarReader newReader = cleaned.get(reader);
+ if (newReader != null) {
+ sweptReaders.add(newReader);
+ afterCleanupSize += newReader.size();
+ }
+ // if these two differ, the former represents the
swept version of the latter
+ if (newReader != reader) {
+ oldReaders.add(reader);
+ }
+ } else {
+ sweptReaders.add(reader);
}
- } else {
- sweptReaders.add(reader);
}
+ readers = sweptReaders;
+ } finally {
+ fileStoreLock.writeLock().unlock();
}
- readers = sweptReaders;
- } finally {
- fileStoreLock.writeLock().unlock();
- }
- tracker.clearSegmentIdTables(reclaimed, gcInfo);
+ tracker.clearSegmentIdTables(reclaimed, gcInfo);
- // Close old readers *after* setting readers to the new readers to
avoid accessing
- // a closed reader from readSegment()
- LinkedList<File> toRemove = newLinkedList();
- for (TarReader oldReader : oldReaders) {
- closeAndLogOnFail(oldReader);
- File file = oldReader.getFile();
- gcListener.info("TarMK GC #{}: cleanup marking file for deletion:
{}", GC_COUNT, file.getName());
- toRemove.addLast(file);
- }
-
- long finalSize = size();
- long reclaimedSize = initialSize - afterCleanupSize;
- stats.reclaimed(reclaimedSize);
- gcJournal.persist(reclaimedSize, finalSize);
- gcListener.cleaned(reclaimedSize, finalSize);
- gcListener.info("TarMK GC #{}: cleanup completed in {} ({} ms). Post
cleanup size is {} ({} bytes)" +
- " and space reclaimed {} ({} bytes).",
- GC_COUNT, watch, watch.elapsed(MILLISECONDS),
- humanReadableByteCount(finalSize), finalSize,
- humanReadableByteCount(reclaimedSize), reclaimedSize);
- return toRemove;
+ // Close old readers *after* setting readers to the new readers to
avoid accessing
+ // a closed reader from readSegment()
+ LinkedList<File> toRemove = newLinkedList();
+ for (TarReader oldReader : oldReaders) {
+ closeAndLogOnFail(oldReader);
+ File file = oldReader.getFile();
+ gcListener.info("TarMK GC #{}: cleanup marking file for
deletion: {}", GC_COUNT, file.getName());
+ toRemove.addLast(file);
+ }
+
+ long finalSize = size();
+ long reclaimedSize = initialSize - afterCleanupSize;
+ stats.reclaimed(reclaimedSize);
+ gcJournal.persist(reclaimedSize, finalSize);
+ gcListener.cleaned(reclaimedSize, finalSize);
+ gcListener.info("TarMK GC #{}: cleanup completed in {} ({} ms).
Post cleanup size is {} ({} bytes)" +
+ " and space reclaimed {} ({} bytes).",
+ GC_COUNT, watch, watch.elapsed(MILLISECONDS),
+ humanReadableByteCount(finalSize), finalSize,
+ humanReadableByteCount(reclaimedSize), reclaimedSize);
+ return toRemove;
+ }
}
private void collectBulkReferences(Set<UUID> bulkRefs) {
@@ -938,26 +967,27 @@ public class FileStore implements Segmen
* 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).
- * @return {@code true} if compaction succeeded, {@code false} otherwise.
+ * @return A {@code Runnable} for cleaning up or {@code null} when compact
failed.
*/
- public boolean compact() throws IOException {
- gcListener.info("TarMK GC #{}: compaction started, gc options={}",
GC_COUNT, gcOptions);
- Stopwatch watch = Stopwatch.createStarted();
-
- SegmentNodeState before = getHead();
- final int newGeneration = getGcGeneration() + 1;
- SegmentBufferWriter bufferWriter = new SegmentBufferWriter(this,
tracker, segmentReader, "c", newGeneration);
- Supplier<Boolean> cancel = newCancelCompactionCondition();
- SegmentNodeState after = compact(bufferWriter, before, cancel);
- if (after == null) {
- gcListener.info("TarMK GC #{}: compaction cancelled: {}.",
GC_COUNT, cancel);
- return false;
- }
+ @CheckForNull
+ public Runnable compact() throws IOException {
+ try (Closeable s = withSemaphore(gcSemaphore)) {
+ Stopwatch watch = Stopwatch.createStarted();
+ gcListener.info("TarMK GC #{}: compaction started, gc options={}",
GC_COUNT, gcOptions);
+
+ SegmentNodeState before = getHead();
+ final int newGeneration = getGcGeneration() + 1;
+ SegmentBufferWriter bufferWriter = new SegmentBufferWriter(this,
tracker, segmentReader, "c", newGeneration);
+ Supplier<Boolean> cancel = newCancelCompactionCondition();
+ SegmentNodeState after = compact(bufferWriter, before, cancel);
+ if (after == null) {
+ gcListener.info("TarMK GC #{}: compaction cancelled: {}.",
GC_COUNT, cancel);
+ return null;
+ }
- gcListener.info("TarMK GC #{}: compacted {} to {}",
- GC_COUNT, before.getRecordId(), after.getRecordId());
+ gcListener.info("TarMK GC #{}: compacted {} to {}",
+ GC_COUNT, before.getRecordId(), after.getRecordId());
- try {
int cycles = 0;
boolean success = false;
while (cycles < gcOptions.getRetryCount() &&
@@ -973,7 +1003,7 @@ public class FileStore implements Segmen
after = compact(bufferWriter, head, cancel);
if (after == null) {
gcListener.info("TarMK GC #{}: compaction cancelled: {}.",
GC_COUNT, cancel);
- return false;
+ return null;
}
gcListener.info("TarMK GC #{}: compacted {} against {} to {}",
@@ -991,7 +1021,7 @@ public class FileStore implements Segmen
cycles++;
success = forceCompact(bufferWriter, or(cancel,
timeOut(forceTimeout, SECONDS)));
if (!success) {
- if(cancel.get()) {
+ if (cancel.get()) {
gcListener.warn("TarMK GC #{}: compaction failed
to force compact remaining commits. " +
"Compaction was cancelled: {}.", GC_COUNT,
cancel);
} else {
@@ -1003,7 +1033,10 @@ public class FileStore implements Segmen
}
if (success) {
- gcScheduler.execute(format("TarMK cleanup [%s]", directory),
new Runnable() {
+ gcListener.compacted(SUCCESS, newGeneration);
+ gcListener.info("TarMK GC #{}: compaction succeeded in {} ({}
ms), after {} cycles",
+ GC_COUNT, watch, watch.elapsed(MILLISECONDS), cycles);
+ return new Runnable() {
@Override
public void run() {
try {
@@ -1012,14 +1045,12 @@ public class FileStore implements Segmen
gcListener.error("TarMK GC #" + GC_COUNT + ":
cleanup failed", e);
}
}
- });
-
- gcListener.compacted(SUCCESS, newGeneration);
- gcListener.info("TarMK GC #{}: compaction succeeded in {} ({}
ms), after {} cycles",
- GC_COUNT, watch, watch.elapsed(MILLISECONDS), cycles);
- return true;
+ };
} else {
- gcScheduler.execute(format("TarMK cleanup [%s]", directory),
new Runnable() {
+ gcListener.compacted(FAILURE, newGeneration);
+ gcListener.info("TarMK GC #{}: compaction failed after {} ({}
ms), and {} cycles",
+ GC_COUNT, watch, watch.elapsed(MILLISECONDS), cycles);
+ return new Runnable() {
@Override
public void run() {
try {
@@ -1039,19 +1070,15 @@ public class FileStore implements Segmen
gcListener.error("TarMK GC #" + GC_COUNT + ":
cleanup failed", e);
}
}
- });
- gcListener.compacted(FAILURE, newGeneration);
- gcListener.info("TarMK GC #{}: compaction failed after {} ({}
ms), and {} cycles",
- GC_COUNT, watch, watch.elapsed(MILLISECONDS), cycles);
- return false;
+ };
}
} catch (InterruptedException e) {
gcListener.error("TarMK GC #" + GC_COUNT + ": compaction
interrupted", e);
currentThread().interrupt();
- return false;
+ return null;
} catch (Exception e) {
gcListener.error("TarMK GC #" + GC_COUNT + ": compaction
encountered an error", e);
- return false;
+ return null;
}
}
@@ -1571,7 +1598,7 @@ public class FileStore implements Segmen
}
@Override
- public boolean compact() {
+ public Runnable compact() {
throw new UnsupportedOperationException("Read Only Store");
}
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=1763538&r1=1763537&r2=1763538&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
Thu Oct 6 09:03:56 2016
@@ -75,7 +75,6 @@ import org.apache.jackrabbit.oak.spi.sta
import org.apache.jackrabbit.oak.stats.Clock;
import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -530,7 +529,7 @@ public class CompactionAndCleanupIT {
public Boolean call() throws IOException {
boolean cancelled = false;
for (int k = 0; !cancelled && k < 1000; k++) {
- cancelled = !fileStore.compact();
+ cancelled = fileStore.compact() == null;
}
return cancelled;
}
@@ -1081,7 +1080,10 @@ public class CompactionAndCleanupIT {
Callable<Void> concurrentCleanupTask = new Callable<Void>() {
@Override
public Void call() throws Exception {
- fileStore.cleanup();
+ // Concurrent cleanup calls are not supported by the file
store
+ synchronized (fileStore) {
+ fileStore.cleanup();
+ }
return null;
}
};
@@ -1141,8 +1143,8 @@ public class CompactionAndCleanupIT {
final Callable<Void> concurrentCleanTask = new Callable<Void>() {
@Override
public Void call() throws Exception {
- // FIXME OAK-4685: Explicitly avoid concurrent cleanup
calls until OAK-4685 is fixed
- synchronized (nodeStore) {
+ // Concurrent cleanup calls are not supported by the file
store
+ synchronized (fileStore) {
fileStore.cleanup();
}
return null;