/**
* 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.jena.sparql.core.mem;
import static com.jayway.awaitility.Awaitility.await ;
import static org.apache.jena.graph.NodeFactory.createBlankNode ;
import static org.apache.jena.query.ReadWrite.READ ;
import static org.apache.jena.query.ReadWrite.WRITE ;
import static org.slf4j.LoggerFactory.getLogger ;
import java.util.concurrent.atomic.AtomicBoolean ;
import org.apache.jena.sparql.core.Quad ;
import org.junit.Assert ;
import org.junit.Test ;
import org.slf4j.Logger ;
public class TestDatasetGraphInMemoryThreading extends Assert {
Logger log = getLogger(TestDatasetGraphInMemoryThreading.class);
Quad q = Quad.create(createBlankNode(), createBlankNode(), createBlankNode(), createBlankNode());
@Test
public void abortedChangesNeverBecomeVisible() {
final DatasetGraphInMemory dsg = new DatasetGraphInMemory();
// flags with which to interleave threads
final AtomicBoolean addedButNotAborted = new AtomicBoolean(false);
final AtomicBoolean addedCheckedButNotAborted = new AtomicBoolean(false);
final AtomicBoolean aborted = new AtomicBoolean(false);
dsg.begin(READ);
assertTrue(dsg.isEmpty()); // no quads present
dsg.end();
// we introduce a Writer thread
new Thread() {
@Override
public void run() {
dsg.begin(WRITE);
log.debug("Writer: Added test quad.");
dsg.add(q);
assertFalse(dsg.isEmpty()); // quad has appeared in this transaction
addedButNotAborted.set(true);
log.debug("Writer: Waiting to abort addition of test quad.");
await().untilTrue(addedCheckedButNotAborted);
assertFalse(dsg.isEmpty()); // quad has appeared, but only inside this transaction
log.debug("Writer: Aborting test quad.");
dsg.abort();
log.debug("Writer: Aborted test quad.");
aborted.set(true);
}
}.start();
// back to Reader code
log.debug("Reader: Waiting for test quad to be added in Writer thread.");
await().untilTrue(addedButNotAborted);
dsg.begin(READ);
assertTrue(dsg.isEmpty()); // no quads present to Reader
dsg.end();
log.debug("Reader: Checked to see test quad is not visible.");
addedCheckedButNotAborted.set(true);
log.debug("Reader: Waiting to see Writer transaction aborted.");
await().untilTrue(aborted);
dsg.begin(READ);
assertTrue(dsg.isEmpty()); // no quads have appeared
dsg.end();
}
@Test
public void snapshotsShouldBeIsolated() {
final DatasetGraphInMemory dsg = new DatasetGraphInMemory();
// flags with which to interleave threads
final AtomicBoolean addedButNotCommitted = new AtomicBoolean(false);
final AtomicBoolean addedCheckedButNotCommitted = new AtomicBoolean(false);
final AtomicBoolean committed = new AtomicBoolean(false);
dsg.begin(READ);
assertTrue(dsg.isEmpty()); // no quads present
dsg.end();
// we introduce a Writer thread
new Thread() {
@Override
public void run() {
dsg.begin(WRITE);
log.debug("Writer: Added test quad.");
dsg.add(q);
assertFalse(dsg.isEmpty()); // quad has appeared, but only in this transaction
addedButNotCommitted.set(true);
log.debug("Writer: Waiting to commit test quad.");
await().untilTrue(addedCheckedButNotCommitted);
log.debug("Writer: Committing test quad.");
dsg.commit();
log.debug("Writer: Committed test quad.");
committed.set(true);
}
}.start();
// back to Reader code
log.debug("Reader: Waiting for test quad to be added in Writer thread.");
await().untilTrue(addedButNotCommitted);
dsg.begin(READ);
assertTrue(dsg.isEmpty()); // still no quads present, because Reader and Writer are isolated
log.debug("Reader: Checked to see test quad is not yet visible.");
addedCheckedButNotCommitted.set(true);
log.debug("Reader: Waiting to see test quad committed.");
await().untilTrue(committed);
assertTrue(dsg.isEmpty()); // still no quads present, because Reader and Writer are isolated
dsg.end();
// but a new transaction should see the results of Writer's action
dsg.begin(READ);
assertFalse(dsg.isEmpty()); // quad has appeared, for new transaction
dsg.end();
}
@Test
public void locksAreCorrectlyDistributed() {
final DatasetGraphInMemory dsg = new DatasetGraphInMemory();
final AtomicBoolean readLockCaptured = new AtomicBoolean(false);
final AtomicBoolean writeLockCaptured = new AtomicBoolean(false);
dsg.begin(WRITE); // acquire the write lock: no other Thread can now acquire it until it is released
new Thread() {
@Override
public void run() {
dsg.begin(READ); // a read lock should always be available except during a commit
readLockCaptured.set(true);
dsg.end();
dsg.begin(WRITE); // this should block until the write lock is released
writeLockCaptured.set(true);
}
}.start();
await().untilTrue(readLockCaptured);
if (writeLockCaptured.get()) fail("Write lock captured by two threads at once!");
dsg.abort();
dsg.end(); // release the write lock to competitor
await().untilTrue(writeLockCaptured);
assertTrue("Lock was not handed over to waiting thread!", writeLockCaptured.get());
}
}