Index: oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java (revision aef68138a849ca472dc8d9e979da3e4de0c43c92) +++ oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileAccess.java (revision cf75cae5bc1036490541c7efa084af42f5b0c5b9) @@ -97,7 +97,7 @@ } @Override - public int length() throws IOException { + public synchronized int length() throws IOException { long length = file.length(); checkState(length < Integer.MAX_VALUE); return (int) length; \ No newline at end of file Index: oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java (revision aef68138a849ca472dc8d9e979da3e4de0c43c92) +++ oak-segment/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java (revision cf75cae5bc1036490541c7efa084af42f5b0c5b9) @@ -21,6 +21,7 @@ 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.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.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.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; @@ -635,6 +642,77 @@ fail("Segment " + id + "should be gc'ed"); } catch (SegmentNotFoundException ignore) {} } finally { + fileStore.close(); + } + } + + @Test + public void randomAccessFileConcurrentReadAndLength() throws Exception { + final FileStore fileStore = FileStore.builder(getFileStoreFolder()) + .withMaxFileSize(1) + .withMemoryMapping(false) + .build(); + + final SegmentNodeStore nodeStore = SegmentNodeStore.builder(fileStore).build(); + CompactionStrategy strategy = new CompactionStrategy(false, false, CLEAN_NONE, 0, (byte) 0) { + @Override + public boolean compacted(@Nonnull Callable setHead) + throws Exception { + return nodeStore.locked(setHead); + } + }; + + 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(); + for (int i = 0; i < 100; i++) { + results.add(executorService.submit(concurrentWriteTask)); + results.add(executorService.submit(concurrentCleanupTask)); + results.add(executorService.submit(concurrentReferenceCollector)); + } + + for (Future result : results) { + assertNull(result.get()); + } + + } finally { + new ExecutorCloser(executorService).close(); fileStore.close(); } }