/*
* Copyright 2010-2012 Luca Garulli (l.garulli--at--orientechnologies.com)
*
* Licensed 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 com.orientechnologies.orient.test.database.auto;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.orientechnologies.common.concur.lock.OOneEntryPerKeyLockManager;
import com.orientechnologies.common.concur.lock.OOneEntryPerKeyLockManager.LOCK;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
/**
* Test class for OLockManager
*
* @author Sylvain Spinelli
*
*/
public class LockManagerTest {
public static final int THREADS = 64;
public static int cyclesByProcess = 10000000;
public static boolean verbose = false;
public static OOneEntryPerKeyLockManager<Callable<?>> lockMgr = new OOneEntryPerKeyLockManager<Callable<?>>(
OGlobalConfiguration.ENVIRONMENT_CONCURRENT
.getValueAsBoolean(),
5000, 10000);
protected List<Callable<?>> resources = new ArrayList<Callable<?>>();
protected List<Thread> processes = Collections.synchronizedList(new ArrayList<Thread>());
protected List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
protected AtomicInteger counter = new AtomicInteger();
public static class ResourceRead implements Callable<Void> {
AtomicInteger countRead = new AtomicInteger(0);
@Override
public Void call() throws Exception {
lockMgr.acquireLock(this, LOCK.SHARED);
try {
countRead.incrementAndGet();
// try {
// Thread.sleep(1 + Math.abs(new Random().nextInt() % 3));
// } catch (Exception e) {
// }
if (verbose)
System.out.println("ResourceRead locked by " + Thread.currentThread());
} finally {
countRead.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.SHARED);
}
return null;
}
}
public static class ResourceWrite implements Callable<Void> {
AtomicInteger countWrite = new AtomicInteger(0);
@Override
public Void call() throws Exception {
lockMgr.acquireLock(this, LOCK.EXCLUSIVE);
try {
countWrite.incrementAndGet();
// try {
// Thread.sleep(1 + Math.abs(new Random().nextInt() % 3));
// } catch (Exception e) {
// }
if (verbose)
System.out.println("ResourceWrite locked by " + Thread.currentThread());
if (countWrite.get() != 1)
throw new AssertionError("countWrite:" + countWrite);
} finally {
countWrite.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.EXCLUSIVE);
}
return null;
}
}
public static class ResourceReadWrite implements Callable<Void> {
AtomicInteger countRead = new AtomicInteger(0);
AtomicInteger countWrite = new AtomicInteger(0);
volatile boolean lastWasRead;
@Override
public Void call() throws Exception {
if (lastWasRead) {
write();
} else {
read();
}
lastWasRead = !lastWasRead;
return null;
}
void read() {
lockMgr.acquireLock(this, LOCK.SHARED);
try {
countRead.incrementAndGet();
if (verbose)
System.out.println("ResourceReadWrite SHARED locked by " + Thread.currentThread());
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
} finally {
countRead.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.SHARED);
}
}
void write() {
lockMgr.acquireLock(this, LOCK.EXCLUSIVE);
try {
countWrite.incrementAndGet();
// try {
// Thread.sleep(1 + Math.abs(new Random().nextInt() % 3));
// } catch (Exception e) {
// }
if (verbose)
System.out.println("ResourceReadWrite EXCLUSIVE locked by " + Thread.currentThread());
if (countWrite.get() != 1)
throw new AssertionError("countWrite:" + countWrite);
if (countRead.get() != 0)
throw new AssertionError("countRead:" + countRead);
} finally {
countWrite.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.EXCLUSIVE);
}
}
}
public static class ResourceReantrance implements Callable<Void> {
AtomicInteger countRead = new AtomicInteger(0);
AtomicInteger countReentrantRead = new AtomicInteger(0);
AtomicInteger countWrite = new AtomicInteger(0);
AtomicInteger countReentrantWrite = new AtomicInteger(0);
@Override
public Void call() throws Exception {
write();
return null;
}
void read() {
lockMgr.acquireLock(this, LOCK.SHARED);
try {
countRead.incrementAndGet();
// while (countRead < 3) {
// // wait for 3 concurrent threads at this point.
// Thread.yield();
// }
reentrantRead();
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
} finally {
countRead.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.SHARED);
}
}
void reentrantRead() {
lockMgr.acquireLock(this, LOCK.SHARED);
try {
countReentrantRead.incrementAndGet();
// while (countRead < 2) {
// // wait an other thread.
// Thread.yield();
// }
// write();
} finally {
countReentrantRead.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.SHARED);
}
}
void write() {
lockMgr.acquireLock(this, LOCK.EXCLUSIVE);
try {
countWrite.incrementAndGet();
reentrantWrite();
// for (int i = 0; i < 10000000; i++) {
// }
// if(log) System.out.println("ResourceReantrance locked by " + Thread.currentThread());
if (countWrite.get() != 1)
throw new AssertionError("countWrite:" + countWrite);
} finally {
countWrite.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.EXCLUSIVE);
}
}
void reentrantWrite() {
lockMgr.acquireLock(this, LOCK.EXCLUSIVE);
try {
countReentrantWrite.incrementAndGet();
read();
// try {
// Thread.sleep(1 + Math.abs(new Random().nextInt() % 3));
// } catch (Exception e) {
// }
if (verbose)
System.out.println("ResourceReantrance locked by " + Thread.currentThread());
if (countReentrantWrite.get() != 1)
throw new AssertionError("countReentrantWrite:" + countReentrantWrite);
} finally {
countReentrantWrite.decrementAndGet();
lockMgr.releaseLock(Thread.currentThread(), this, LOCK.EXCLUSIVE);
}
}
}
public class Process implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < cyclesByProcess; i++) {
for (Callable<?> res : resources) {
if (exceptions.size() > 0)
return;
res.call();
counter.incrementAndGet();
}
}
} catch (Throwable e) {
e.printStackTrace();
exceptions.add(e);
}
}
}
@Test
public void testConcurrentAccess() throws Throwable {
final long start = System.currentTimeMillis();
// for (int i = 0; i < 10; i++)
resources.add(new ResourceRead());
resources.add(new ResourceWrite());
resources.add(new ResourceReadWrite());
resources.add(new ResourceReantrance());
System.out.println("Starting " + THREADS + " threads: ");
for (int i = 0; i < THREADS; ++i) {
if (i % THREADS / 10 == 0)
System.out.print('.');
processes.add(new Thread(new Process()));
}
for (Thread thread : processes) {
thread.start();
}
System.out.println("\nOk, waiting for end: ");
for (int i = 0; i < THREADS; ++i) {
if (i % THREADS / 10 == 0)
System.out.print('.');
processes.get(i).join();
}
System.out.println("\nOk, all threads back : " + counter.get() + " in: " + ((System.currentTimeMillis() - start) / 1000f)
+ " secs");
// Pulish exceptions.
if (exceptions.size() > 0) {
for (Throwable exc : exceptions) {
exc.printStackTrace();
}
throw exceptions.get(0);
}
Assert.assertEquals(counter.get(), processes.size() * resources.size() * cyclesByProcess);
Assert.assertEquals(lockMgr.getCountCurrentLocks(), 0);
}
}