/*
* 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.rdf.model.test ;
import junit.framework.*;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.Lock ;
public class TestConcurrency extends TestSuite
{
/** Creates new RDQLTestSuite */
static public TestSuite suite() {
return new TestConcurrency();
}
// Test suite to exercise the locking
static long SLEEP = 100 ;
static int threadCount = 0;
// Note : reuse the model across tests.
final static Model model1 = ModelFactory.createDefaultModel() ;
final static Model model2 = ModelFactory.createDefaultModel() ;
public TestConcurrency()
{
super("Model concurrency control") ;
if ( true )
{
// Same model: inner and outer
addTest(new Nesting("Lock nesting 1 - same model",
model1, Lock.READ, Lock.READ, false)) ;
addTest(new Nesting("Lock nesting 2 - same model",
model1, Lock.WRITE, Lock.WRITE, false)) ;
addTest(new Nesting("Lock nesting 3 - same model",
model1, Lock.READ, Lock.WRITE, true)) ;
addTest(new Nesting("Lock nesting 4 - same model",
model1, Lock.WRITE, Lock.READ, false)) ;
// Different model: inner and outer
addTest(new Nesting("Lock nesting 1 - different models",
model1, Lock.READ, model2, Lock.READ, false)) ;
addTest(new Nesting("Lock nesting 2 - different models",
model1, Lock.WRITE, model2, Lock.WRITE, false)) ;
addTest(new Nesting("Lock nesting 3 - different models",
model1, Lock.READ, model2, Lock.WRITE, false)) ;
addTest(new Nesting("Lock nesting 4 - different models",
model1, Lock.WRITE, model2, Lock.READ, false)) ;
}
if ( true )
{
// Crude test
addTest(new Parallel("Parallel concurrency test")) ;
}
}
static class Nesting extends TestCase
{
Model outerModel ;
Model innerModel ;
boolean outerLock ;
boolean innerLock ;
boolean exceptionExpected ;
// Same model
Nesting(String testName, Model model,
boolean lock1, boolean lock2, boolean exExpected)
{
this(testName, model, lock1, model, lock2, exExpected) ;
}
// Potetnially different models
Nesting(String testName,
Model model1, boolean lock1,
Model model2, boolean lock2, boolean exExpected)
{
super(testName);
outerModel = model1 ;
outerLock = lock1 ;
innerModel = model2 ;
innerLock = lock2 ;
exceptionExpected = exExpected ;
}
@Override
protected void runTest()
{
boolean gotException = false ;
try {
outerModel.enterCriticalSection(outerLock) ;
try {
try {
// Should fail if outerLock is READ and innerLock is WRITE
// and its on the same model, inner and outer.
innerModel.enterCriticalSection(innerLock) ;
} finally { innerModel.leaveCriticalSection() ; }
} catch (Exception ex)
{
gotException = true ;
}
} finally {
outerModel.leaveCriticalSection() ;
}
if ( exceptionExpected )
assertTrue("Failed to get expected lock promotion error", gotException) ;
else
assertTrue("Got unexpected lock promotion error", !gotException) ;
}
}
static class Parallel extends TestCase
{
int threadTotal = 10 ;
Parallel(String testName)
{
super(testName) ;
}
@Override
protected void runTest()
{
Model model = ModelFactory.createDefaultModel() ;
Thread threads[] = new Thread[threadTotal] ;
boolean getReadLock = Lock.READ ;
for (int i = 0; i < threadTotal; i++)
{
String nextId = "T"+Integer.toString(++threadCount);
threads[i] = new Operation(model, getReadLock) ;
threads[i].setName(nextId) ;
threads[i].start() ;
getReadLock = ! getReadLock ;
}
boolean problems = false ;
for ( int i = 0; i < threadTotal; i++)
{
try { threads[i].join(200*SLEEP) ; } catch (InterruptedException intEx) {}
}
// Try again for any we missed.
for ( int i = 0; i < threadTotal; i++)
{
if ( threads[i].isAlive() )
try { threads[i].join(200*SLEEP) ; } catch (InterruptedException intEx) {}
if ( threads[i].isAlive())
{
System.out.println("Thread "+threads[i].getName()+" failed to finish") ;
problems = true ;
}
}
assertTrue("Some thread failed to finish", !problems) ;
}
class Operation extends Thread
{
Model model ;
boolean readLock ;
Operation(Model m, boolean withReadLock)
{
model = m ; readLock = withReadLock ;
}
@Override
public void run()
{
for ( int i = 0 ; i < 2 ; i++ )
{
try {
model.enterCriticalSection(readLock) ;
if ( readLock )
readOperation(false) ;
else
writeOperation(false) ;
} finally { model.leaveCriticalSection() ; }
}
}
}
// Operations ----------------------------------------------
volatile int writers = 0 ;
// The example model operations
void doStuff(String label, boolean doThrow)
{
String id = Thread.currentThread().getName() ;
// Puase a while to cause other threads to (try to) enter the region.
try { Thread.sleep(SLEEP) ; } catch (InterruptedException intEx){}
if ( doThrow )
throw new RuntimeException(label) ;
}
// Example operations
public void readOperation(boolean doThrow)
{
if ( writers > 0 )
System.err.println("Concurrency error: writers around!") ;
doStuff("read operation", false) ;
if ( writers > 0 )
System.err.println("Concurrency error: writers around!") ;
}
public void writeOperation(boolean doThrow)
{
writers++ ;
doStuff("write operation", false) ;
writers-- ;
}
}
}