How to Test if Your Tomcat Web Application Is Thread Safe
In this quick tutorial, a Java dev shows us the ropes (or should I say threads?) of thread safety in Tomcat-based web applications.
Join the DZone community and get the full member experience.
Join For Freein the following, i want to show you how to test if your tomcat web application is thread-safe. as an example application, i use jenkins deployed on an apache tomcat 9.0.
to detect concurrency bugs during our tests we use vmlens . vmlens traces the test execution and analyzes the trace afterward. it detects deadlocks and race conditions during the test run.
testing
to enable vmlens we add it as java agent to the catalina_opts in catalina.sh on linux or catalina.bat on windows:
catalina_opts="-javaagent:<path to agent> -xmx8g"
we also set a high enough heap size. after running jenkins and executing some build jobs we see the following report in vmlens:
analyzing
let us look at one of the races found, the race at accessing the field hudson.udpbroadcastthread.shutdown.
the thread "jenkins udp 33848 monitoring thread" reads the field in the race and the thread "localhost-startstop-2" writes it. let us look at the class and the reading method
run()
and the writing method
shutdown()
.
public class udpbroadcastthread extends thread {
private boolean shutdown;
public void run() {
try {
mcs.joingroup(multicast);
ready.signal();
while(true) {
byte[] buf = new byte[2048];
datagrampacket p = new datagrampacket(buf,buf.length);
mcs.receive(p);
socketaddress sender = p.getsocketaddress();
// prepare a response
tcpslaveagentlistener tal = jenkins.gettcpslaveagentlistener();
stringbuilder rsp = new stringbuilder("<hudson>");
tag(rsp,"version", jenkins.version);
tag(rsp,"url", jenkins.getrooturl());
tag(rsp,"server-id", jenkins.getlegacyinstanceid());
tag(rsp,"slave-port",tal==null?null:tal.getport());
for (udpbroadcastfragment f : udpbroadcastfragment.all())
f.buildfragment(rsp,sender);
rsp.append("</hudson>");
byte[] response = rsp.tostring().getbytes("utf-8");
mcs.send(new datagrampacket(response,response.length,sender));
}
} catch (closedbyinterruptexception e) {
// shut down
} catch (socketexception e) {
if (shutdown) { // forcibly closed
return;
} // if we failed to listen to udp, just silently abandon it, as a stack trace
// makes people unnecessarily concerned, for a feature that currently does no good.
logger.log(level.info, "cannot listen to udp port {0}, skipping: {1}", new object[] {port, e});
logger.log(level.fine, null, e);
} catch (ioexception e) {
if (shutdown) return; // forcibly closed
logger.log(level.warning, "udp handling problem",e);
udphandlingproblem = true;
}
}
public void shutdown() {
shutdown = true;
mcs.close();
interrupt();
}
}
the field shutdown is a nonvolatile field. it is read in line 28 and 35 in the method run and written in line 41 in the method shutdown
since the field hudson.udpbroadcastthread.shutdown is not volatile, it is not guaranteed that the "jenkins udp 33848 monitoring thread" sees the values set by the "localhost-startstop-2" thread.
the "jenkins udp 33848 monitoring thread" might, for example, run on the first core while "localhost-startstop-2" runs on the second core of a multi-core cpu. the write to a normal field does not invalidate the cache of the cores. therefore the "jenkins udp 33848 monitoring thread" still sees the cached old value.
Published at DZone with permission of Thomas Krieger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments