diff --git oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedScheduler.java oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedScheduler.java index c33b07f105..56c9439d5b 100644 --- oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedScheduler.java +++ oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedScheduler.java @@ -267,7 +267,7 @@ public class LockBasedScheduler implements Scheduler { if (revisions.setHead(before.getRecordId(), after.getRecordId())) { head.set(after); contentChanged(after.getChildNode(ROOT), commit.info()); - refreshHead(true); +// refreshHead(true); return head.get().getChildNode(ROOT); } diff --git oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedSchedulerTest.java oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedSchedulerTest.java new file mode 100644 index 0000000000..f2f1db0ddd --- /dev/null +++ oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/scheduler/LockBasedSchedulerTest.java @@ -0,0 +1,116 @@ +/* + * 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.scheduler; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nonnull; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser; +import org.apache.jackrabbit.oak.segment.RecordId; +import org.apache.jackrabbit.oak.segment.SegmentNodeState; +import org.apache.jackrabbit.oak.segment.memory.MemoryStore; +import org.apache.jackrabbit.oak.spi.commit.CommitHook; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class LockBasedSchedulerTest { + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(new File("target")); + + private NodeState getRoot(Scheduler scheduler) { + return scheduler.getHeadNodeState().getChildNode("root"); + } + + @Test + public void testSimulatedRaceOnRevisions() throws Exception { + final MemoryStore ms = new MemoryStore(); + final LockBasedScheduler scheduler = LockBasedScheduler.builder(ms.getRevisions(), ms.getReader()).build(); + + final RecordId initialHead = ms.getRevisions().getHead(); + ExecutorService executorService = newFixedThreadPool(10); + final AtomicInteger count = new AtomicInteger(); + final Random rand = new Random(); + + try { + Callable commitTask = new Callable() { + @Override + public PropertyState call() throws Exception { + String property = "prop" + count.incrementAndGet(); + Commit commit = createCommit(scheduler, property, "value"); + SegmentNodeState result = (SegmentNodeState) scheduler.schedule(commit); + + return result.getProperty(property); + } + }; + + Callable parallelTask = new Callable() { + @Override + public Void call() throws Exception { + Thread.sleep(rand.nextInt(10)); + ms.getRevisions().setHead(ms.getRevisions().getHead(), initialHead); + return null; + } + }; + + List> results = newArrayList(); + for (int i = 0; i < 100; i++) { + results.add(executorService.submit(commitTask)); + executorService.submit(parallelTask); + } + + for (Future result : results) { + assertNotNull(result.get()); + } + } finally { + new ExecutorCloser(executorService).close(); + } + } + + private Commit createCommit(final Scheduler scheduler, final String property, String value) { + NodeBuilder a = getRoot(scheduler).builder(); + a.setProperty(property, value); + + Commit commit = new Commit(a, new CommitHook() { + @Override + @Nonnull + public NodeState processCommit(NodeState before, NodeState after, CommitInfo info) { + return after; + } + }, CommitInfo.EMPTY); + + return commit; + } +}