/**
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.objectweb.speedo.runtime.concurrency;
import org.objectweb.speedo.AbstractSpeedo;
import org.objectweb.speedo.SpeedoTestHelper;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.pobjects.userid.BasicB;
import org.objectweb.speedo.pobjects.basic.BasicA;
import org.objectweb.util.monolog.api.BasicLevel;
import javax.jdo.PersistenceManager;
import javax.jdo.JDOException;
import javax.jdo.JDOFatalException;
import javax.jdo.PersistenceManagerFactory;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.Iterator;
/**
* Tests the high concurrency of the Speedo (more than 100 users)
*
* @author S.Chassande-Barrioz
*/
public class TestManyUsers extends SpeedoTestHelper {
public static final int W_ACTION = 1;
public static final int R_W_ACTION = 2;
public static final int MIXED_ACTION = 3;
public static Vector rollbackExceptions = new Vector();
public static Vector errors = new Vector();
public TestManyUsers(String s) {
super(s);
}
protected String getLoggerName() {
return LOG_NAME + ".rt.concurrency.TestManyUsers";
}
/**
* Tests the concurrency of the initialization of a class (mapping)
* (1000 users)
*/
public void testLoading() {
final int nbThread = Integer.getInteger(
"conform.org.objectweb.speedo.rt.concurrency.TestManyUsers.testLoading.thread",
1000).intValue();
logger.log(BasicLevel.INFO, "Run concurrent Loading, " + nbThread + " threads");
Thread[] ts = new Thread[nbThread];
for (int i = 0; i < nbThread; i++) {
ts[i] = new Thread(
new Runnable() {
public void run() {
try {
PersistenceManager pm = pmf.getPersistenceManager();
Thread.sleep(1);
pm.getObjectIdClass(BasicB.class);
pm.close();
Assert.assertTrue(true);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
}
);
}
for (int i = 0; i < nbThread; i++) {
ts[i].start();
}
try {
for (int i = 0; i < nbThread; i++) {
ts[i].join();
}
} catch (InterruptedException e) {
fail(e.getMessage());
}
}
/**
* Tests the concurrency of several writer on a same persistent object.
* (500 users). Action: a.writeF2(a.readF2() + 1)
*/
public void testConcurrentReadAndWrite() {
logger.log(BasicLevel.INFO, "");
int nbThread = Integer.getInteger(
"conform.org.objectweb.speedo.rt.concurrency.TestManyUsers.testConcurrentReadAndWrite.thread",
200).intValue();
logger.log(BasicLevel.INFO, "Run concurrent readAndWrite actions");
int[] actions = new int[10];
Arrays.fill(actions, R_W_ACTION);
testAccess(nbThread, actions);
}
/**
* Tests the concurrency of several writer on a same persistent object.
* (500 users). Action = a.inc()
*/
public void testConcurrentWrite() {
logger.log(BasicLevel.INFO, "");
int nbThread = Integer.getInteger(
"conform.org.objectweb.speedo.rt.concurrency.TestManyUsers.testConcurrentWrite.thread",
200).intValue();
logger.log(BasicLevel.INFO, "Run concurrent write actions");
int[] actions = new int[10];
Arrays.fill(actions, W_ACTION);
testAccess(nbThread, actions);
}
/**
* Tests the concurrency of several writer on a same persistent object.
* (500 users). Action = a.inc()
*/
public void testConcurrentMixed() {
logger.log(BasicLevel.INFO, "");
int nbThread = Integer.getInteger(
"conform.org.objectweb.speedo.rt.concurrency.TestManyUsers.testConcurrentWrite.thread",
200).intValue();
logger.log(BasicLevel.INFO, "Run concurrent mixed actions");
int[] actions = new int[10];
Arrays.fill(actions, MIXED_ACTION);
testAccess(nbThread, actions);
}
/**
* Tests the concurrency of several writer on a same persistent object.
* (500 users)
*/
public void testAccess(final int nbThread , final int[] actions) {
final int initialValue = 0;
rollbackExceptions.clear();
errors.clear();
Thread[] ts = new Thread[nbThread];
BasicA ba = new BasicA();
ba.writeF1("1");
ba.writeF2(initialValue);
PersistenceManager pm = pmf.getPersistenceManager();
pm.currentTransaction().begin();
pm.makePersistent(ba);
final BasicA a = ba;
Object id = pm.getObjectId(ba);
pm.currentTransaction().commit();
pm.close();
for (int i = 0; i < nbThread; i++) {
final int _i = i;
ts[i] = new Thread(
new Runnable() {
public void run() {
int action = (_i % 2 == 0 ? W_ACTION : R_W_ACTION);
for(int j=0; j<actions.length; j++) {
PersistenceManager pm = pmf.getPersistenceManager();
boolean rollback = false;
try {
pm.currentTransaction().begin();
logger.log(BasicLevel.DEBUG, _i + "," + j + " begin tx");
switch(actions[j]) {
case W_ACTION:
a.incF2();
break;
case R_W_ACTION:
a.writeF2(a.readF2() + 1);
break;
case MIXED_ACTION:
if (action == W_ACTION) {
a.incF2();
action = R_W_ACTION;
} else if (action == R_W_ACTION) {
a.writeF2(a.readF2() + 1);
action = W_ACTION;
}
break;
}
pm.currentTransaction().commit();
logger.log(BasicLevel.DEBUG, _i + "," + j + " finished");
} catch (JDOFatalException e) {
rollback = true;
rollbackExceptions.add(e);
logger.log(BasicLevel.DEBUG, "Tx " + _i + "," + j + " has been rolledback");
} catch (Exception e) {
Exception ie = ExceptionHelper.getNested(e);
errors.add(ie);
logger.log(BasicLevel.ERROR, _i + "," + j + " has a problem", ie);
pm.currentTransaction().rollback();
} finally {
try {
pm.close();
} catch (JDOException e) {
logger.log(BasicLevel.ERROR, "tx " + _i
+ "," + j + " has been "
+ (rollback ? "rolledback" : "committed")
+ " and the close occurs an error", ExceptionHelper.getNested(e));
throw e;
}
}
}
}
}
);
}
long execTime = System.currentTimeMillis();
for (int i = 0; i < nbThread; i++) {
ts[i].start();
}
int val = 0;
ArrayList al = new ArrayList(nbThread);
try {
logger.log(BasicLevel.INFO, nbThread + " threads launched doing " + actions.length + " actions, waiting them ...");
for (int i = 0; i < nbThread; i++) {
ts[i].join(1000);
if (ts[i].isAlive()) {
al.add(new Integer(i));
logger.log(BasicLevel.DEBUG, i + " is not finished after" +
" the delay, it could be blocked");
}
}
String dg = getDG(pmf);
if (dg != null) {
logger.log(BasicLevel.INFO, dg);
}
if (al.size() > 0) {
for (Iterator it = al.iterator(); it.hasNext();) {
int th = ((Integer) it.next()).intValue();
if (!ts[th].isAlive()) {
logger.log(BasicLevel.DEBUG, th + " is late but ok.");
it.remove();
}
}
}
execTime = System.currentTimeMillis() - execTime;
if (al.size() > 0) {
logger.log(BasicLevel.INFO, "Kill alive threads");
for (Iterator it = al.iterator(); it.hasNext();) {
int th = ((Integer) it.next()).intValue();
if (!ts[th].isAlive()) {
it.remove();
} else {
try {
ts[th].interrupt();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
fail("Thread " + al + " blocked!");
}
} catch (InterruptedException e) {
//fail(e.getMessage());
} finally {
if (al.size() == 0) {
logger.log(BasicLevel.DEBUG, "Auto cleaning");
pm = pmf.getPersistenceManager();
ba = (BasicA) pm.getObjectById(id, false);
val = ba.readF2();
pm.currentTransaction().begin();
pm.deletePersistent(ba);
pm.currentTransaction().commit();
pm.close();
int nbCommittedTx = nbThread*actions.length - errors.size() - rollbackExceptions.size();
logger.log(BasicLevel.INFO, "Commited transaction rate: "
+ ((nbCommittedTx * 100) / (nbThread*actions.length)) + "%"
+ ", exec time: " + execTime + "ms"
+ ", tx/s:" + ((nbCommittedTx * 1000)/execTime));
}
}
if (errors.size() > 0) {
fail("There are " + errors.size() + "/" + nbThread + " errors during the run");
}
if (al.size() == 0) {
Assert.assertEquals("Bad f2 value",
initialValue + nbThread*actions.length - rollbackExceptions.size(), val);
}
}
private static String getDG(PersistenceManagerFactory pmf) {
Map m = null;
try {
m = ((AbstractSpeedo) pmf).getDependencyGraph().getVertexes();
} catch (Exception e) {
e.printStackTrace();
return null;
}
if (m.size() == 0) {
return null;
}
StringBuffer sb = new StringBuffer("dependency Graph: ");
List waiters = new ArrayList(m.keySet());
Set s = new HashSet(waiters.size() * 2);
s.addAll(m.keySet());
for (Iterator it = ((Collection) m.values()).iterator(); it.hasNext();) {
s.addAll((Collection) it.next());
}
List all = new ArrayList(s);
for (int i = 0; i <all.size() ; i++) {
Object t1 = all.get(i);
int t1Idx = all.indexOf(t1);
Collection dependencies = (Collection) m.get(t1);
if (dependencies != null) {
for (Iterator it = dependencies.iterator(); it.hasNext();) {
sb.append("\nws");
sb.append(t1Idx);
sb.append(" = > ");
sb.append("ws");
sb.append(all.indexOf(it.next()));
}
}
}
return sb.toString();
}
}