package org.xmlBlaster.test.memoryleak;
import org.xmlBlaster.util.ThreadLister;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.client.qos.ConnectQos;
import org.xmlBlaster.client.qos.DisconnectQos;
import org.xmlBlaster.client.qos.ConnectReturnQos;
import org.xmlBlaster.util.qos.SessionQos;
import org.xmlBlaster.client.I_XmlBlasterAccess;
import org.xmlBlaster.client.I_Callback;
import org.xmlBlaster.client.key.UpdateKey;
import org.xmlBlaster.client.qos.UpdateQos;
import org.xmlBlaster.util.MsgUnit;
import org.xmlBlaster.test.Util;
import junit.framework.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.ArrayList;
import java.util.Iterator;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
/**
* This does a twofold test by creating a number of connection, an
* almost infinite number of times. It tests two stuff:
* 1. if the client side is leaking threads.
* 2. if Jacorb contains a locking bug.
*
* @author <a href="mailto:pra@tim.se">Peter Antman</a>
* @version $Revision: 1.4 $
*/
public class TestThreadLeak extends TestCase implements I_Callback {
private static String ME = "TestThreadLeak";
private final Global glob;
private static Logger log = Logger.getLogger(TestThreadLeak.class.getName());
private String fileName;
private int noConnections = 10;
private boolean noError = true;
private ArrayList connections = new ArrayList();
private int maxThreadDiff = 500;
private String pid;
private String osName;
/** Time a connection should live, before beeing taken down */
private long cttl = 5000;
public TestThreadLeak (Global glob, String testName) throws Exception{
super(testName);
this.glob = glob;
fileName = glob.getProperty().get("pidFileName", (String)null);
}
/**
* Sets up the fixture.
*/
protected void setUp() throws Exception
{
String[] args = {
"-protocol",
"SOCKET", //"SOCKET",
"-session.maxSessions",
"20"
};
glob.init(args);
// if we have a filename where a pid is, wait until that file has shown up
// But now more that 5 times
if ( fileName != null) {
int i = 0;
File file = new File(fileName);
while (!file.exists()) {
i++;
if ( i > 4) {
Assert.fail("We where given a pid filename " + file + " but could not find it, giving up");
} // end of if ()
Thread.sleep(2000);
} // end of while ()
BufferedReader r = new BufferedReader(new FileReader(file));
pid = r.readLine();
} // end of if ()
osName = System.getProperty("os.name");
}
void dumpThreadStack() throws Exception {
if ( pid != null && !osName.startsWith("Window")) {
Runtime runtime = Runtime.getRuntime();
Process p = runtime.exec("kill -3 " + pid);
p.waitFor();
} else {
log.info("Could not dump stack pid="+pid+" os="+osName);
} // end of else
}
void handleLock(ConnectorWorker wr) throws Exception {
dumpThreadStack();
//Assert.fail("Thread lock in connector worker "+wr + " giving up");
}
public void testThreadLeakage() throws Exception {
int startNoThreads = -1;
int lastNoThreads = -1;
int round = 0;
while ( noError ) {
round++;
log.info("Doing a new connection round no " + round);
for ( int i = 0; i< noConnections;i++) {
ConnectorWorker conn = new ConnectorWorker(glob, cttl);
connections.add(conn);
} // end of for ()
// Wait a while
System.gc();
Thread.sleep(1000);
System.gc();
Thread.sleep(1000);
// Count threads, if more than maxThreadDiff has been created since
// the first round: fail
int noThreads = ThreadLister.countThreads();
if (startNoThreads != -1 ) {
startNoThreads = noThreads;
lastNoThreads = noThreads;
} else {
// Check how many since first round
int firstDiff = noThreads - startNoThreads;
int lastDiff = noThreads - lastNoThreads;
log.info("No of thread created since start:"+firstDiff+"; number of threads created since last round: " + lastDiff);
lastNoThreads = noThreads;
if ( firstDiff > maxThreadDiff) {
ThreadLister.listAllThreads(System.out);
Assert.fail("Max number of new threads reached " +firstDiff +
" number of threads created since first round: XmlBlaster is leaking huge numbers of threads. Happened in round " + round);
} // end of if ()
} // end of else
// Wait a while for connections to stop
Thread.sleep( noConnections*1000 );
// Check that all connections are finished.
Iterator c = connections.iterator();
while ( c.hasNext() ) {
ConnectorWorker w = (ConnectorWorker)c.next();
// Check that its NOT alive
if ( w.isAlive() ) {
// Opps, do we have a lock here
// We give it five rounds if its still in Connecting state we abort
int j = 0;
while (w.isAlive() && j < 4) {
log.warning("Possible lock of connection " + w + " detected, waiting 30 s round "+j);
j++;
Thread.sleep(30*1000);
if ( j > 3) {
log.severe("Possible lock of connection " + w + " detected, aborting");
noError = false;
handleLock(w);
} // end of if ()
} // end of while ()
} // end of if ()
Throwable t = w.getException();
if ( t != null) {
log.severe("Connection had exception, giving up : "+t);
t.printStackTrace();
Assert.fail("Connection had exception, giving up : "+t);
} // end of if ()
} // end of while ()
connections.clear();
} // end of while ()
}
/**
* This is the callback method invoked from xmlBlaster
* delivering us a new asynchronous message.
* @see org.xmlBlaster.client.I_Callback#update(String, UpdateKey,
byte[], UpdateQos)
*/
public String update(String cbSessionId, UpdateKey updateKey, byte[]
content, UpdateQos updateQos)
{
log.info("Receiving update of a message " + updateKey.getOid() + " for subId: " + updateQos.getSubscriptionId() );
log.fine("Got message " + new String(content));
return "";
}
/**
* Method is used by TestRunner to load these tests
*/
public static Test suite() throws Exception
{
TestSuite suite= new TestSuite();
suite.addTest(new TestThreadLeak(new Global(),
"testThreadLeakage"));
return suite;
}
/**
* Invoke:
* <pre>
* java org.xmlBlaster.test.mime.TestXPathSubscribeFilter
* java -Djava.compiler= junit.textui.TestRunner -noloading
org.xmlBlaster.test.mime.TestXPathSubscribeFilter
* <pre>
*/
public static void main(String args[]) throws Exception
{
try {
Global glob = new Global();
if (glob.init(args) != 0) {
System.err.println(ME + ": Init failed");
System.exit(1);
}
TestThreadLeak testSub = new TestThreadLeak(glob, "testThreadLeak");
testSub.setUp();
testSub.testThreadLeakage();
testSub.tearDown();
} catch (Throwable e) {
e.printStackTrace();
System.exit(0);
} // end of try-catch
}
/**
* Connect to XmlBlaster and disconnect after timeout milis.
*/
class ConnectorWorker implements Runnable {
Global glob;
long timeout;
private I_XmlBlasterAccess con = null;
Thread internalThread;
Throwable ie;
ConnectReturnQos retQos;
volatile String state = "CREATED";
volatile long started;
public ConnectorWorker(Global glob, long timeout) {
this.glob = glob.getClone(null);
this.timeout = timeout;
internalThread = new Thread(this);
internalThread.start();
}
public void run() {
started = System.currentTimeMillis();
state = "RUNNING";
if ( Thread.currentThread() != internalThread ) {
ie = new RuntimeException("Only internal thread allowed");
throw (RuntimeException)ie;
} // end of if ()
try {
// Connect
state = "CONNECTING";
con = glob.getXmlBlasterAccess();
ConnectQos qos = new ConnectQos(glob, "test", "dummy");
retQos = con.connect(qos, TestThreadLeak.this); // Login to xmlBlaster
log.info("Connected "+ this);
state = "CONNECTED";
Thread.sleep(timeout);
// Disconnect
state = "DISCONNECTING";
con.disconnect(null);
state = "DISCONNECTED";
con=null;
glob.shutdown();
glob = null;
} catch (Throwable e) {
ie = e;
log.severe("Giving up " + e);
} // end of try-catch
}
public long getAgeSeconds() {
return (System.currentTimeMillis()-started)/1000;
}
public String getState() {
return state;
}
public String toString() {
String s = super.toString();
String q = "";
if ( retQos != null) {
SessionQos ses = retQos.getSessionQos() ;
q = "secretId="+ses.getSecretSessionId() + " publicId="+ses.getPublicSessionId();
} // end of if ()
return s+":"+state+" age="+getAgeSeconds()+" seconds old, (thread="+internalThread.getName()+") "+q;
}
public Throwable getException() {
return ie;
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
} // TestThreadLeak