/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.resource.lock.pessimistic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.log4j.Logger;
import org.olat.basesecurity.ManagerFactory;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.id.Identity;
import org.olat.core.test.OlatTestCase;
/**
* Tests the PLock functionality.
* <p>
* Note that a frequent error case for the PLockTest is if the underlying
* MySQL Database does not have the InnoDB configured (or the InnoDB engine
* cannot be launched/has startup problems).
* Check the status of your tables in MySQL via 'show table status' to see
* if all tables are set to InnoDB!
*/
public class PLockTest extends OlatTestCase {
private static final int MAX_COUNT = 5; //5; //30;
private static final int MAX_USERS_MORE = 20; //20; //100;
private static Logger log = Logger.getLogger(PLockTest.class.getName());
private static boolean isInitialized = false;
/**
* @param name
*/
public PLockTest(String name) {
super(name);
}
/**
* @return Test
*/
public static Test suite() {
return new TestSuite(PLockTest.class);
}
public void testReentrantLock() {
long start = System.currentTimeMillis();
String asset = "p1";
// make sure the lock is created first
PLock pc = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc);
DBFactory.getInstance().closeSession();
// test double acquisition within same transaction
PLock pc1 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc1);
PLock pc2 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc2);
DBFactory.getInstance().closeSession();
// and without explicit transaction boundary.
PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(p1);
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(p2);
long stop = System.currentTimeMillis();
long diff = stop - start;
assertTrue("5 select's took longer than 10 seconds -> deadlock / lock timeout ? dur in ms was:"+diff, diff < 10000);
}
/**
* T1 T2
*
*/
public void testReentrantLock2Threads() {
long start = System.currentTimeMillis();
final String asset = "p1-2";
final int thread1Running = 0;
final int thread2Running = 0;
// make sure the lock is created first
PLock pc = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc);
DBFactory.getInstance().closeSession();
final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1));
final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1));
// thread 1
new Thread(new Runnable() {
public void run() {
try {
PLock pc1 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc1);
System.out.println("Thread-1: got PLock pc1=" + pc1);
System.out.println("Thread-1: sleep 10sec");
sleep(10000);
PLock pc2 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(pc2);
System.out.println("Thread-1: got PLock pc2=" + pc2);
System.out.println("Thread-1: finished");
statusList.add(Boolean.TRUE);
} catch (Exception e) {
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
// ignore
};
}
}}).start();
// thread 2
new Thread(new Runnable() {
public void run() {
try {
System.out.println("Thread-2: sleep 5sec");
sleep(5000);
System.out.println("Thread-2: try to get PLock...");
PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(p1);
System.out.println("Thread-2: got PLock p1=" + p1);
System.out.println("Thread-2: sleep 10sec");
sleep(10000);
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock(asset);
assertNotNull(p2);
System.out.println("Thread-1: got PLock p2=" + p2);
System.out.println("Thread-1: finished");
statusList.add(Boolean.TRUE);
} catch (Exception e) {
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
// ignore
};
}
}}).start();
// sleep until t1 and t2 should have terminated/excepted
int loopCount = 0;
while ( (statusList.size()<2) && (exceptionHolder.size()<1) && (loopCount<60)) {
sleep(1000);
loopCount++;
}
assertTrue("Threads did not finish in 60sec", loopCount<60);
// if not -> they are in deadlock and the db did not detect it
for (Exception exception : exceptionHolder) {
System.out.println("exception: "+exception.getMessage());
exception.printStackTrace(System.out);
}
assertTrue("exception in test => see sysout", exceptionHolder.size() == 0);
}
public void testLockWaitTimout() {
System.out.println("testing if holding a lock timeouts");
// make sure all three row entries for the locks are created, otherwise the system-wide locking
// applied on lock-row-creation cannot support row-level-locking by definition.
PLock pc3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(pc3);
DBFactory.getInstance().closeSession();
/**
* t1 t2
* .. bli
* .. ..
* .. ..
* .. ..
* bli ..
* ..
* ..
* .... hold for longer than 30 secs
*
*/
final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1));
// t1
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
sleep(2500);
PLock p3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p3);
} catch (Exception e) {
e.printStackTrace(System.out);
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
e.printStackTrace(System.out);
// ignore
};
System.out.println("Thread t1 quits.");
}
}});
t1.start();
// t2
Thread t2=new Thread(new Runnable() {
public void run() {
try {
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p2);
sleep(60000);
// holding the lock for more than the transaction timeout (normally 30secs, configured where? hib) should cause a lock timeout
// if the db is configured so.
} catch (Exception e) {
e.printStackTrace(System.out);
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
e.printStackTrace(System.out);
// ignore
};
System.out.println("Thread t2 quits.");
}
}});
t2.start();
// sleep until t1 and t2 should have terminated/excepted
sleep(55000);
System.out.println("Thread t1 alive: "+t1.isAlive());
System.out.println("Thread t2 alive: "+t2.isAlive());
// if not -> they are in deadlock and the db did not detect it
System.out.println("Number of exceptions: "+exceptionHolder.size()+" (expecting > 0)");
for (Exception exception : exceptionHolder) {
System.out.println("exception: "+exception.getMessage());
exception.printStackTrace(System.out);
}
assertTrue("expected a lock wait timeout exceeded exception", exceptionHolder.size() > 0);
}
public void testSingleRowLockingSupported() {
System.out.println("testing if one lock only locks the given row and not the complete table (test whether the database supports rowlocking)");
// make sure both row entries for the locks are created, otherwise the system-wide locking
// applied on lock-row-creation cannot support row-level-locking by definition.
PLock pc1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(pc1);
PLock pc2 = PessimisticLockManager.getInstance().findOrPersistPLock("blublu");
assertNotNull(pc2);
DBFactory.getInstance().closeSession();
final List<Long> holder = new ArrayList<Long>(1);
// first thread acquires the lock and waits and continues holding the lock for some time.
PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(p1);
new Thread(new Runnable() {
public void run() {
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock("blublu");
assertNotNull(p2);
long p2Acquired = System.currentTimeMillis();
holder.add(new Long(p2Acquired));
DBFactory.getInstance().closeSession();
}}).start();
sleep(5000);
long p1AboutToRelease= System.currentTimeMillis();
DBFactory.getInstance().closeSession();
// if row locking is not supported, then the timestamp when p2 has been acquired will be shortly -after- p1 has been released
boolean singleRowLockingOk = holder.size() >0 && holder.get(0).longValue() < p1AboutToRelease;
assertTrue("the database does not seem to support row locking when executing 'select for update', critical for performance!, ", singleRowLockingOk);
}
public void testNestedLockingSupported() {
System.out.println("testing if nested locking is supported");
// make sure all three row entries for the locks are created, otherwise the system-wide locking
// applied on lock-row-creation cannot support row-level-locking by definition.
PLock pc1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(pc1);
PLock pc2 = PessimisticLockManager.getInstance().findOrPersistPLock("blublu");
assertNotNull(pc2);
PLock pc3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(pc3);
DBFactory.getInstance().closeSession();
final List<Long> holder = new ArrayList<Long>(1);
// first thread acquires the two locks and waits and continues holding the lock for some time.
PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(p1);
PLock p3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p3);
new Thread(new Runnable() {
public void run() {
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p2);
long p2Acquired = System.currentTimeMillis();
holder.add(new Long(p2Acquired));
DBFactory.getInstance().closeSession();
}}).start();
sleep(5000);
boolean acOk = holder.size() == 0;
DBFactory.getInstance().closeSession();
sleep(5000);
boolean acNowOk = holder.size() == 1;
// if row locking is not supported, then the timestamp when p2 has been acquired will be shortly -after- p1 has been released
assertTrue("since holding the blabla lock, no other may acquire it", acOk);
assertTrue("after having released the blabla lock, a next waiting thread must have acquired it after some time", acNowOk);
}
public void testDeadLockTimeout() {
System.out.println("testing if deadlock detection and handling is supported");
// make sure all three row entries for the locks are created, otherwise the system-wide locking
// applied on lock-row-creation cannot support row-level-locking by definition.
PLock pc1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(pc1);
PLock pc2 = PessimisticLockManager.getInstance().findOrPersistPLock("blublu");
assertNotNull(pc2);
PLock pc3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(pc3);
DBFactory.getInstance().closeSession();
/**
* t1 t2
* bla bli
* .. ..
* .. ..
* .. ..
* bli ..
* ..
* ..
* bla
* -> deadlock! t2 waits on bla (already acquired by t1, but t1 waits on bli, already acquired by t2)
*
*/
final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1));
// t1
new Thread(new Runnable() {
public void run() {
try {
PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(p1);
sleep(2500);
// now try to acquire blibli but that fails, since blibli is already locked by thread 2.
// but thread 2 cannot continue either, since it is waiting for lock blabla, which is already hold by thread 1
// -> deadlock
PLock p3 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p3);
} catch (Exception e) {
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
// ignore
};
}
}}).start();
// t2
new Thread(new Runnable() {
public void run() {
try {
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock("blibli");
assertNotNull(p2);
sleep(5000);
PLock p3 = PessimisticLockManager.getInstance().findOrPersistPLock("blabla");
assertNotNull(p3);
} catch (Exception e) {
exceptionHolder.add(e);
} finally {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
// ignore
};
}
}}).start();
// sleep until t1 and t2 should have terminated/excepted
sleep(8000);
// if not -> they are in deadlock and the db did not detect it
for (Exception exception : exceptionHolder) {
System.out.println("exception: "+exception.getMessage());
exception.printStackTrace(System.out);
}
assertTrue("expected a deadlock exception, but got none", exceptionHolder.size() > 0);
}
public void testPerf() {
System.out.println("testing what the throughput is for the pessimistic locking");
// test what the throughput is for the pessimistic locking.
// take 500 threads (created and started with no delay (as fast as the vm can) trying to acquire a plock on 20 different olatresourceables.
// measure how long that takes. and warn if it exceeds an upper boundary.
// the server is assumed to have around 2GHz cpu and 2GBytes RAM.
// the first thread to acquire a new olatres will first lock on the global lock and then create the new entry to lock upon.
// we therefore also measure how long it takes again when all locks have already been inserted into the plock table.
// results: on a std. laptop with postgres 8, 500 threads with 20 resourceables take about 3000ms (thread creation inclusive)
// -> about
// 1. prepare collection
int numthreads = 500;
int numores = 1;
int maxwait = 12; // seconds to wait for completion (upper performance boundary)
class Collector {
private int threadsDone = 0;
synchronized void incThreadDone() {
threadsDone++;
}
synchronized int getThreadsDoneCnt() {
return threadsDone;
}
};
// 2. create 500 threads and start them
long start = System.currentTimeMillis();
final Collector c = new Collector();
for (int i = 0; i < numthreads; i++) {
final String asset = "assetaboutaslongasores"+(i % numores);
Runnable r = new Runnable() {
public void run() {
PessimisticLockManager.getInstance().findOrPersistPLock(asset);
c.incThreadDone();
DBFactory.getInstance().closeSession();
}
};
new Thread(r).start();
}
int i;
// 4. wait till all are finished or it takes too long
for (i = 0; i < maxwait; i++) {
int donecnt = c.getThreadsDoneCnt();
if (donecnt < numthreads) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//
}
} else { // done
break;
}
}
long stop = System.currentTimeMillis();
System.out.println("perf for Plocktest:testPerf(): "+(stop-start));
assertTrue("performance is not within boundary:"+maxwait, i<maxwait);
// repeat the same again - this time it should/could be faster
// 2. create 500 threads and start them
long start2 = System.currentTimeMillis();
final Collector c2 = new Collector();
for (int i2 = 0; i2 < numthreads; i2++) {
final String asset = "assetaboutaslongasores"+(i2 % numores);
Runnable r = new Runnable() {
public void run() {
PessimisticLockManager.getInstance().findOrPersistPLock(asset);
c2.incThreadDone();
DBFactory.getInstance().closeSession();
}
};
new Thread(r).start();
}
// 4. wait till all are finished or it takes too long
for (i = 0; i < maxwait; i++) {
int donecnt = c2.getThreadsDoneCnt();
if (donecnt < numthreads) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//
}
} else { // done
break;
}
}
long stop2 = System.currentTimeMillis();
System.out.println("perf (again) for Plocktest:testPerf(): "+(stop2-start2));
assertTrue("performance is not within boundary:"+maxwait, i<maxwait);
}
public void testSync() {
System.out.println("testing enrollment");
// ------------------ now check with lock -------------------
// create a group
// create users
final List<Identity> identities = new ArrayList<Identity>();
for (int i = 0; i < MAX_COUNT + MAX_USERS_MORE; i++) {
Identity i1 = ManagerFactory.getManager().createAndPersistIdentity("u"+i, null, null, null, null);
identities.add(i1);
System.out.println("testSync: Identity=" + "u"+i + "created");
DBFactory.getInstance().closeSession();
}
final SecurityGroup group2 = ManagerFactory.getManager().createAndPersistSecurityGroup();
// make sure the lock has been written to the disk (tests for createOrFind see other methods)
DBFactory.getInstance().closeSession();
//PLock p1 = PessimisticLockManager.getInstance().findOrPersistPLock("befinsert");
//assertNotNull(p1);
// try to enrol all in the same group
for (int i = 0; i < MAX_COUNT + MAX_USERS_MORE; i++) {
final int j = i;
new Thread(new Runnable(){
public void run() {
try {
System.out.println("testSync: thread started j=" + j);
Identity id = identities.get(j);
//
PLock p2 = PessimisticLockManager.getInstance().findOrPersistPLock("befinsert");
assertNotNull(p2);
doNoLockingEnrol(id, group2);
DBFactory.getInstance().closeSession();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}}).start();
}
sleep(20000);
// now count
DBFactory.getInstance().closeSession();
int cnt2 = ManagerFactory.getManager().countIdentitiesOfSecurityGroup(group2);
assertTrue("cnt should be smaller or eq than allowed since synced with select for update. cnt:"+cnt2+", max "+MAX_COUNT, cnt2 <= MAX_COUNT);
assertTrue("cnt should be eq to allowed since synced with select for update. cnt:"+cnt2+", max "+MAX_COUNT, cnt2 == MAX_COUNT);
System.out.println("cnt lock "+cnt2);
}
void doNoLockingEnrol(Identity i, SecurityGroup group) {
// check that below max
try {
StringBuilder sb = new StringBuilder();
int cnt = ManagerFactory.getManager().countIdentitiesOfSecurityGroup(group);
sb.append("enrol:cnt:"+cnt);
if (cnt < MAX_COUNT) {
// now sleep a while to allow others to think also that there is still space left in the group
sleep(100);
// now add the user to the security group
sb.append(" adding "+i.getName()+": current.. "+cnt+", max = "+MAX_COUNT);
ManagerFactory.getManager().addIdentityToSecurityGroup(i, group);
}
System.out.println(sb.toString());
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
/**
*
* @param milis the duration in miliseconds to sleep
*/
private void sleep(int milis) {
try {
Thread.sleep(milis);
} catch (InterruptedException e) {
e.printStackTrace(System.out);
}
}
/*
* (non-Javadoc)
*
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
if (PLockTest.isInitialized == false) {
PLockTest.isInitialized = true;
}
DBFactory.getJunitInstance().clearDatabase();
// now simulate the cluster module startup
PessimisticLockManager.getInstance().init();
DBFactory.getInstance().closeSession();
}
/*
* (non-Javadoc)
*
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
try {
DBFactory.getInstance().closeSession();
} catch (Exception e) {
log.error("tearDown failed: ", e);
}
}
}