/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.clients.fcp;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.TestCase;
import freenet.clients.fcp.FCPPluginConnection.SendDirection;
import freenet.pluginmanager.FredPluginFCPMessageHandler.ClientSideFCPMessageHandler;
import freenet.pluginmanager.FredPluginFCPMessageHandler.ServerSideFCPMessageHandler;
public final class FCPPluginConnectionImplTest extends TestCase {
/**
* {@link FCPPluginConnectionImpl#sendSynchronous(SendDirection, FCPPluginMessage, long)} is
* powered by an internal map which keeps track of synchronous sends which are waiting for a
* reply.<br>
* As this map is accessed concurrently, one might suspect possible thread safety issues.<br>
* This test therefore runs 100 sendSynchronous() threads in parallel to trigger race
* conditions, and thereby checks the following:<br>
* - Whether replies are delivered to the correct thread. This is done by having each thread
* send a message with a certain index number, to which the server replies with the same index
* number. The reply is checked to have the same index number as the original message.<br>
* - Whether the map which keeps track of synchronous sends does not leak. This is done by
* checking whether it is empty after all send threads have terminated.<br>
*/
public final void testSendSynchronousThreadSafety() throws InterruptedException {
// JUnit ignores failures in threads other than the threads which it runs tests from.
// Thus we pass failures out with this boolean.
// NOTICE: We also use JUnit assert*() / fail() even though they won't work in threads
// - to produce logging on stderr so you can tell where the failure happened. When adding
// more of those, make sure to do failure.set(true) BEFORE the assert*() / fail() as they
// will throw.
final AtomicBoolean failure = new AtomicBoolean(false);
final FCPPluginConnectionImpl connection = FCPPluginConnectionImpl.constructForUnitTest(
new ServerSideFCPMessageHandler() {
@Override public FCPPluginMessage handlePluginFCPMessage(
final FCPPluginConnection connection, final FCPPluginMessage message) {
final FCPPluginMessage reply = FCPPluginMessage.constructSuccessReply(message);
reply.params.putSingle("replyToThread", message.params.get("thread"));
return reply;
}
},
new ClientSideFCPMessageHandler() {
@Override public FCPPluginMessage handlePluginFCPMessage(
final FCPPluginConnection connection, final FCPPluginMessage message) {
failure.set(true);
fail("This test is about sendSynchronous() so the reply messages should not "
+ "hit the client message handler");
throw new UnsupportedOperationException();
}
});
final int threadCount = 100;
final Thread[] threads = new Thread[threadCount];
for(int i=0; i < threadCount; ++i) {
final String threadIndex = Integer.toString(i);
final Thread thread = new Thread(new Runnable() {
final FCPPluginMessage message;
{
message = FCPPluginMessage.construct();
message.params.putSingle("thread", threadIndex);
}
@Override public void run() {
try {
final FCPPluginMessage reply = connection.sendSynchronous(
SendDirection.ToServer, message, TimeUnit.SECONDS.toNanos(10));
if(!threadIndex.equals(reply.params.get("replyToThread"))) {
failure.set(true);
}
assertEquals(threadIndex, reply.params.get("replyToThread"));
} catch (IOException e) {
failure.set(true);
fail("IOException " + e);
} catch (InterruptedException e) {
failure.set(true);
fail("InterruptedException " + e);
}
}
});
threads[i] = thread;
}
// Start them in a separate loop, not in the loop where we construct them, to ensure that
// they are all started at the same time, execute in parallel, and thus have maximal
// probability of race conditions.
for(int i=0; i < threadCount; ++i)
threads[i].start();
for(int i=0; i < threadCount; ++i)
threads[i].join();
assertEquals("JUnit failures cannot be passed out of threads, please check stdout/stderr.",
false, failure.get());
assertEquals("FCPPluginConnectionImpl sendSynchronous() map should not leak",
0, connection.getSendSynchronousCount());
}
}