/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.jobs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import com.slamd.common.Constants;
import com.slamd.job.JobClass;
/**
* This class defines a thread that works in conjunction with the TCPReplay job
* to read any data returned on any of the connections associated with the job
* threads.
*
*
* @author Neil A. Wilson
*/
public class TCPReplayReadThread
extends Thread
{
// The list of pending connections that need to be registered with the
// selector.
private ArrayList<SocketChannel> pendingConnectionList;
// Indicates whether this thread has been started.
private boolean threadStarted;
// Indicates whether we have determined internally that we should stop the
// selector because there are no more threads registered with it.
private boolean stopSelector;
// The value used to keep track of the number of threads that are currently
// registered to use this read thread.
private int registeredThreads;
// A mutex used to provide threadsafe operations for this thread.
private final Object threadMutex;
// The selector that will be used to multiplex the reads.
private Selector selector;
// The TCPReplay job instance with which this thread is associated.
private TCPReplayJobClass tcpReplayJob;
/**
* Creates a new instance of this TCPReplay read thread that will be used to
* read data from connections associated with the provided TCPReplay job.
*
* @param tcpReplayJob The TCPReplay job instance for which this thread
* should read all data from the server.
*
* @throws IOException If a problem occurs while creating the associated
* selector.
*/
public TCPReplayReadThread(TCPReplayJobClass tcpReplayJob)
throws IOException
{
this.tcpReplayJob = tcpReplayJob;
setName("TCP Replay Read Thread");
selector = Selector.open();
pendingConnectionList = new ArrayList<SocketChannel>();
registeredThreads = 0;
threadStarted = false;
stopSelector = false;
threadMutex = new Object();
}
/**
* Registers a provided job thread with this read thread to indicate that it
* will be sending connections to this thread for reading.
*/
public void registerJobThread()
{
synchronized (threadMutex)
{
registeredThreads++;
if (! threadStarted)
{
start();
threadStarted = true;
}
}
}
/**
* Deregisters a job thread from this read thread to indicate that it will no
* longer be sending connections to this thread for reading.
*/
public void threadDone()
{
synchronized (threadMutex)
{
registeredThreads--;
if (registeredThreads <= 0)
{
stopSelector = true;
}
}
}
/**
* Registers the provided socket channel with this read thread so that any
* incoming data available on that connection will be read.
*
* @param socketChannel The socket channel to be registered. It must
* already be configured in non-blocking mode.
*/
public void registerSocketChannel(SocketChannel socketChannel)
{
synchronized (threadMutex)
{
pendingConnectionList.add(socketChannel);
}
selector.wakeup();
}
/**
* Loops, waiting for data to be available on any of the registered
* connections, reading it as it becomes available.
*/
@Override()
public void run()
{
// Allocate the buffer that we will use to read data from clients.
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
// Loop, reading data from clients until we decide that we should stop for
// some reason.
boolean consecutiveFailures = false;
while (! (tcpReplayJob.shouldStop() || stopSelector))
{
try
{
// See if there is any data available for reading.
int selectedKeys = selector.select(100);
if (selectedKeys > 0)
{
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext())
{
SelectionKey key = (SelectionKey) iterator.next();
if (key.isReadable())
{
SocketChannel channel = (SocketChannel) key.channel();
if (channel.isConnected())
{
int bytesRead = channel.read(buffer);
buffer.clear();
while (bytesRead > 0)
{
bytesRead = channel.read(buffer);
buffer.clear();
}
if (bytesRead < 0)
{
key.cancel();
}
}
else
{
key.cancel();
}
}
iterator.remove();
}
}
// See if there are any pending connections that need to be registered.
synchronized (threadMutex)
{
if (! pendingConnectionList.isEmpty())
{
Iterator iterator = pendingConnectionList.iterator();
while (iterator.hasNext())
{
SocketChannel channel = (SocketChannel) iterator.next();
channel.register(selector, SelectionKey.OP_READ);
}
pendingConnectionList.clear();
}
}
consecutiveFailures = false;
}
catch (Exception e)
{
if (consecutiveFailures)
{
tcpReplayJob.logMessage("Exception caught while trying to read " +
"data from a client: " +
JobClass.stackTraceToString(e));
stopSelector = true;
}
else
{
consecutiveFailures = true;
tcpReplayJob.writeVerbose("Exception caught while trying to read " +
"data from a client: " +
JobClass.stackTraceToString(e));
}
}
}
// If we've gotten here, then we can't do any more good. Request that the
// job stop and close any connections that may still be associated with the
// selector.
if (consecutiveFailures)
{
tcpReplayJob.stopJob(Constants.JOB_STATE_STOPPED_DUE_TO_ERROR);
}
else
{
tcpReplayJob.stopJob(Constants.JOB_STATE_COMPLETED_SUCCESSFULLY);
}
synchronized (threadMutex)
{
Iterator iterator = selector.keys().iterator();
while (iterator.hasNext())
{
try
{
SelectionKey key = (SelectionKey) iterator.next();
key.channel().close();
key.cancel();
} catch (Exception e) {}
}
}
}
}