diff --git oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java index 086adfb..7459c60 100644 --- oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java +++ oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java @@ -97,11 +97,10 @@ abstract class FileAccess { } @Override - public int length() throws IOException { + public synchronized int length() throws IOException { long length = file.length(); checkState(length < Integer.MAX_VALUE); - return (int) length; - } + return (int) length; } @Override public synchronized ByteBuffer read(int position, int length) diff --git oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java index 6041ca7..eb8c8fe 100644 --- oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java +++ oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java @@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.plugins.segment; import static com.google.common.collect.Lists.newArrayList; import static java.lang.Integer.getInteger; +import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.MINUTES; import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; import static org.apache.jackrabbit.oak.api.Type.STRING; @@ -33,6 +34,7 @@ import static org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStr import static org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.CleanupType.CLEAN_OLD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -47,9 +49,12 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; @@ -59,6 +64,8 @@ import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser; +import org.apache.jackrabbit.oak.plugins.blob.ReferenceCollector; import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy; import org.apache.jackrabbit.oak.plugins.segment.file.FileStore; import org.apache.jackrabbit.oak.spi.commit.CommitInfo; @@ -639,6 +646,71 @@ public class CompactionAndCleanupIT { } } + @Test + public void randomAccessFileConcurrentReadAndLength() throws Exception { + final FileStore fileStore = FileStore.builder(getFileStoreFolder()) + .withMaxFileSize(1) + .withMemoryMapping(false) + .build(); + + final SegmentNodeStore nodeStore = SegmentNodeStore.builder(fileStore).build(); + + ExecutorService executorService = newFixedThreadPool(300); + final AtomicInteger counter = new AtomicInteger(); + final ReferenceCollector dummyCollector = new ReferenceCollector() { + + @Override + public void addReference(String reference, String nodeId) { + // do nothing + } + }; + + try { + Callable concurrentWriteTask = new Callable() { + @Override + public Void call() throws Exception { + NodeBuilder builder = nodeStore.getRoot().builder(); + builder.setProperty("blob-" + counter.getAndIncrement(), createBlob(nodeStore, 25 * 25)); + nodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + fileStore.flush(); + return null; + } + }; + + Callable concurrentCleanupTask = new Callable() { + @Override + public Void call() throws Exception { + fileStore.cleanup(); + return null; + } + }; + + Callable concurrentReferenceCollector = new Callable() { + @Override + public Void call() throws Exception { + fileStore.getTracker().collectBlobReferences(dummyCollector); + return null; + } + }; + + List> results = newArrayList(); + results.add(executorService.submit(concurrentCleanupTask)); + + for (int i = 0; i < 100; i++) { + results.add(executorService.submit(concurrentWriteTask)); + results.add(executorService.submit(concurrentReferenceCollector)); + } + + for (Future result : results) { + assertNull(result.get()); + } + + } finally { + new ExecutorCloser(executorService).close(); + fileStore.close(); + } + } + private static void addContent(NodeBuilder builder) { for (int k = 0; k < 10000; k++) { builder.setProperty(UUID.randomUUID().toString(), UUID.randomUUID().toString());