Work on #74, updated javadocs, reformatted code to google style guide, added missing final modifiers

This commit is contained in:
Narendra Pathai 2015-09-12 17:46:24 +05:30
parent aebd69efb4
commit 8d429525dc
12 changed files with 908 additions and 875 deletions

View File

@ -10,19 +10,17 @@ import com.iluwatar.reactor.framework.NioServerSocketChannel;
import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
/**
* This application demonstrates Reactor pattern. The example demonstrated is a Distributed Logging Service
* where it listens on multiple TCP or UDP sockets for incoming log requests.
* This application demonstrates Reactor pattern. The example demonstrated is a Distributed Logging
* Service where it listens on multiple TCP or UDP sockets for incoming log requests.
*
* <p>
* <i>INTENT</i>
* <br/>
* <i>INTENT</i> <br/>
* The Reactor design pattern handles service requests that are delivered concurrently to an
* application by one or more clients. The application can register specific handlers for processing
* which are called by reactor on specific events.
*
* <p>
* <i>PROBLEM</i>
* <br/>
* <i>PROBLEM</i> <br/>
* Server applications in a distributed system must handle multiple clients that send them service
* requests. Following forces need to be resolved:
* <ul>
@ -33,8 +31,28 @@ import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
* </ul>
*
* <p>
* The application utilizes single thread to listen for requests on all ports. It does not create
* a separate thread for each client, which provides better scalability under load (number of clients
* <i>PARTICIPANTS</i> <br/>
* <ul>
* <li>Synchronous Event De-multiplexer</li> {@link NioReactor} plays the role of synchronous event
* de-multiplexer. It waits for events on multiple channels registered to it in an event loop.
*
* <p>
* <li>Initiation Dispatcher</li> {@link NioReactor} plays this role as the application specific
* {@link ChannelHandler}s are registered to the reactor.
*
* <p>
* <li>Handle</li> {@link AbstractNioChannel} acts as a handle that is registered to the reactor.
* When any events occur on a handle, reactor calls the appropriate handler.
*
* <p>
* <li>Event Handler</li> {@link ChannelHandler} acts as an event handler, which is bound to a
* channel and is called back when any event occurs on any of its associated handles. Application
* logic resides in event handlers.
* </ul>
*
* <p>
* The application utilizes single thread to listen for requests on all ports. It does not create a
* separate thread for each client, which provides better scalability under load (number of clients
* increase).
*
* <p>
@ -49,6 +67,7 @@ public class App {
/**
* App entry.
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
@ -57,6 +76,7 @@ public class App {
/**
* Starts the NIO reactor.
*
* @throws IOException if any channel fails to bind.
*/
public void start() throws IOException {
@ -66,26 +86,25 @@ public class App {
reactor = new NioReactor(new ThreadPoolDispatcher(2));
/*
* This represents application specific business logic that dispatcher will call
* on appropriate events. These events are read events in our example.
* This represents application specific business logic that dispatcher will call on appropriate
* events. These events are read events in our example.
*/
LoggingHandler loggingHandler = new LoggingHandler();
/*
* Our application binds to multiple channels and uses same logging handler to handle
* incoming log requests.
* Our application binds to multiple channels and uses same logging handler to handle incoming
* log requests.
*/
reactor
.registerChannel(tcpChannel(6666, loggingHandler))
.registerChannel(tcpChannel(6667, loggingHandler))
.registerChannel(udpChannel(6668, loggingHandler))
.start();
reactor.registerChannel(tcpChannel(6666, loggingHandler)).registerChannel(tcpChannel(6667, loggingHandler))
.registerChannel(udpChannel(6668, loggingHandler)).start();
}
/**
* Stops the NIO reactor. This is a blocking call.
*
* @throws InterruptedException if interrupted while stopping the reactor.
*/
public void stop() {
public void stop() throws InterruptedException {
reactor.stop();
}

View File

@ -21,10 +21,11 @@ import java.util.concurrent.TimeUnit;
* @author npathai
*/
public class AppClient {
private ExecutorService service = Executors.newFixedThreadPool(4);
private final ExecutorService service = Executors.newFixedThreadPool(4);
/**
* App client entry.
*
* @throws IOException if any I/O error occurs.
*/
public static void main(String[] args) throws IOException {
@ -34,6 +35,7 @@ public class AppClient {
/**
* Starts the logging clients.
*
* @throws IOException if any I/O error occurs.
*/
public void start() throws IOException {
@ -71,8 +73,8 @@ public class AppClient {
*/
static class TCPLoggingClient implements Runnable {
private int serverPort;
private String clientName;
private final int serverPort;
private final String clientName;
/**
* Creates a new TCP logging client.
@ -119,8 +121,8 @@ public class AppClient {
* A logging client that sends requests to Reactor on UDP socket.
*/
static class UDPLoggingClient implements Runnable {
private String clientName;
private InetSocketAddress remoteAddress;
private final String clientName;
private final InetSocketAddress remoteAddress;
/**
* Creates a new UDP logging client.
@ -140,8 +142,7 @@ public class AppClient {
for (int i = 0; i < 4; i++) {
String message = clientName + " - Log request: " + i;
DatagramPacket request = new DatagramPacket(message.getBytes(),
message.getBytes().length, remoteAddress);
DatagramPacket request = new DatagramPacket(message.getBytes(), message.getBytes().length, remoteAddress);
socket.send(request);

View File

@ -8,8 +8,8 @@ import com.iluwatar.reactor.framework.ChannelHandler;
import com.iluwatar.reactor.framework.NioDatagramChannel.DatagramPacket;
/**
* Logging server application logic. It logs the incoming requests on standard console and returns
* a canned acknowledgement back to the remote peer.
* Logging server application logic. It logs the incoming requests on standard console and returns a
* canned acknowledgement back to the remote peer.
*
* @author npathai
*/
@ -23,8 +23,8 @@ public class LoggingHandler implements ChannelHandler {
@Override
public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) {
/*
* As this handler is attached with both TCP and UDP channels we need to check whether
* the data received is a ByteBuffer (from TCP channel) or a DatagramPacket (from UDP channel).
* As this handler is attached with both TCP and UDP channels we need to check whether the data
* received is a ByteBuffer (from TCP channel) or a DatagramPacket (from UDP channel).
*/
if (readObject instanceof ByteBuffer) {
doLogging(((ByteBuffer) readObject));
@ -40,7 +40,8 @@ public class LoggingHandler implements ChannelHandler {
private void sendReply(AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) {
/*
* Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming message.
* Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming
* message.
*/
DatagramPacket replyPacket = new DatagramPacket(ByteBuffer.wrap(ACK));
replyPacket.setReceiver(incomingPacket.getSender());

View File

@ -10,28 +10,29 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* This represents the <i>Handle</i> of Reactor pattern. These are resources managed by OS
* which can be submitted to {@link NioReactor}.
* This represents the <i>Handle</i> of Reactor pattern. These are resources managed by OS which can
* be submitted to {@link NioReactor}.
*
* <p>
* This class serves has the responsibility of reading the data when a read event occurs and
* writing the data back when the channel is writable. It leaves the reading and writing of
* data on the concrete implementation. It provides a block writing mechanism wherein when
* any {@link ChannelHandler} wants to write data back, it queues the data in pending write queue
* and clears it in block manner. This provides better throughput.
* This class serves has the responsibility of reading the data when a read event occurs and writing
* the data back when the channel is writable. It leaves the reading and writing of data on the
* concrete implementation. It provides a block writing mechanism wherein when any
* {@link ChannelHandler} wants to write data back, it queues the data in pending write queue and
* clears it in block manner. This provides better throughput.
*
* @author npathai
*
*/
public abstract class AbstractNioChannel {
private SelectableChannel channel;
private ChannelHandler handler;
private Map<SelectableChannel, Queue<Object>> channelToPendingWrites = new ConcurrentHashMap<>();
private final SelectableChannel channel;
private final ChannelHandler handler;
private final Map<SelectableChannel, Queue<Object>> channelToPendingWrites = new ConcurrentHashMap<>();
private NioReactor reactor;
/**
* Creates a new channel.
*
* @param handler which will handle events occurring on this channel.
* @param channel a NIO channel to be wrapped.
*/
@ -55,7 +56,8 @@ public abstract class AbstractNioChannel {
}
/**
* The operation in which the channel is interested, this operation is provided to {@link Selector}.
* The operation in which the channel is interested, this operation is provided to
* {@link Selector}.
*
* @return interested operation.
* @see SelectionKey
@ -70,8 +72,8 @@ public abstract class AbstractNioChannel {
public abstract void bind() throws IOException;
/**
* Reads the data using the key and returns the read data. The underlying channel should be fetched using
* {@link SelectionKey#channel()}.
* Reads the data using the key and returns the read data. The underlying channel should be
* fetched using {@link SelectionKey#channel()}.
*
* @param key the key on which read event occurred.
* @return data read.
@ -87,8 +89,8 @@ public abstract class AbstractNioChannel {
}
/*
* Called from the context of reactor thread when the key becomes writable.
* The channel writes the whole pending block of data at once.
* Called from the context of reactor thread when the key becomes writable. The channel writes the
* whole pending block of data at once.
*/
void flush(SelectionKey key) throws IOException {
Queue<Object> pendingWrites = channelToPendingWrites.get(key.channel());
@ -119,9 +121,9 @@ public abstract class AbstractNioChannel {
* when this method returns. It will be written when the channel is flushed.
*
* <p>
* This method is used by the {@link ChannelHandler} to send reply back to the client.
* <br/>
* This method is used by the {@link ChannelHandler} to send reply back to the client. <br/>
* Example:
*
* <pre>
* <code>
* {@literal @}Override

View File

@ -7,8 +7,8 @@ import java.nio.channels.SelectionKey;
* to it by the {@link Dispatcher}. This is where the application logic resides.
*
* <p>
* A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and whenever
* an event occurs on any of the associated channels, the handler is notified of the event.
* A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and
* whenever an event occurs on any of the associated channels, the handler is notified of the event.
*
* @author npathai
*/

View File

@ -4,15 +4,15 @@ import java.nio.channels.SelectionKey;
/**
* Represents the event dispatching strategy. When {@link NioReactor} senses any event on the
* registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write
* or connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the I/O
* processing from application specific processing.
* <br/>
* Dispatcher should call the {@link ChannelHandler} associated with the channel on which event occurred.
* registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write or
* connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the
* I/O processing from application specific processing. <br/>
* Dispatcher should call the {@link ChannelHandler} associated with the channel on which event
* occurred.
*
* <p>
* The application can customize the way in which event is dispatched such as using the reactor thread to
* dispatch event to channels or use a worker pool to do the non I/O processing.
* The application can customize the way in which event is dispatched such as using the reactor
* thread to dispatch event to channels or use a worker pool to do the non I/O processing.
*
* @see SameThreadDispatcher
* @see ThreadPoolDispatcher
@ -21,9 +21,9 @@ import java.nio.channels.SelectionKey;
*/
public interface Dispatcher {
/**
* This hook method is called when read event occurs on particular channel. The data read
* is provided in <code>readObject</code>. The implementation should dispatch this read event
* to the associated {@link ChannelHandler} of <code>channel</code>.
* This hook method is called when read event occurs on particular channel. The data read is
* provided in <code>readObject</code>. The implementation should dispatch this read event to the
* associated {@link ChannelHandler} of <code>channel</code>.
*
* <p>
* The type of <code>readObject</code> depends on the channel on which data was received.
@ -36,6 +36,8 @@ public interface Dispatcher {
/**
* Stops dispatching events and cleans up any acquired resources such as threads.
*
* @throws InterruptedException if interrupted while stopping dispatcher.
*/
void stop();
void stop() throws InterruptedException;
}

View File

@ -15,14 +15,14 @@ import java.nio.channels.SelectionKey;
*/
public class NioDatagramChannel extends AbstractNioChannel {
private int port;
private final int port;
/**
* Creates a {@link DatagramChannel} which will bind at provided port and use <code>handler</code> to handle
* incoming events on this channel.
* Creates a {@link DatagramChannel} which will bind at provided port and use <code>handler</code>
* to handle incoming events on this channel.
* <p>
* Note the constructor does not bind the socket, {@link #bind()} method should be called for binding
* the socket.
* Note the constructor does not bind the socket, {@link #bind()} method should be called for
* binding the socket.
*
* @param port the port to be bound to listen for incoming datagram requests.
* @param handler the handler to be used for handling incoming requests on this channel.
@ -35,14 +35,15 @@ public class NioDatagramChannel extends AbstractNioChannel {
@Override
public int getInterestedOps() {
/* there is no need to accept connections in UDP, so the channel shows interest in
* reading data.
/*
* there is no need to accept connections in UDP, so the channel shows interest in reading data.
*/
return SelectionKey.OP_READ;
}
/**
* Reads and returns a {@link DatagramPacket} from the underlying channel.
*
* @return the datagram packet read having the sender address.
*/
@Override
@ -51,8 +52,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
SocketAddress sender = ((DatagramChannel) key.channel()).receive(buffer);
/*
* It is required to create a DatagramPacket because we need to preserve which
* socket address acts as destination for sending reply packets.
* It is required to create a DatagramPacket because we need to preserve which socket address
* acts as destination for sending reply packets.
*/
buffer.flip();
DatagramPacket packet = new DatagramPacket(buffer);
@ -82,8 +83,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
}
/**
* Writes the pending {@link DatagramPacket} to the underlying channel sending data to
* the intended receiver of the packet.
* Writes the pending {@link DatagramPacket} to the underlying channel sending data to the
* intended receiver of the packet.
*/
@Override
protected void doWrite(Object pendingWrite, SelectionKey key) throws IOException {
@ -93,7 +94,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
/**
* Writes the outgoing {@link DatagramPacket} to the channel. The intended receiver of the
* datagram packet must be set in the <code>data</code> using {@link DatagramPacket#setReceiver(SocketAddress)}.
* datagram packet must be set in the <code>data</code> using
* {@link DatagramPacket#setReceiver(SocketAddress)}.
*/
@Override
public void write(Object data, SelectionKey key) {
@ -126,6 +128,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
/**
* Sets the sender address of this packet.
*
* @param sender the sender address.
*/
public void setSender(SocketAddress sender) {
@ -141,6 +144,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
/**
* Sets the intended receiver address. This must be set when writing to the channel.
*
* @param receiver the receiver address.
*/
public void setReceiver(SocketAddress receiver) {

View File

@ -12,43 +12,42 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern.
* Multiple handles i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks
* for events from all these handles. Whenever an event occurs on any of the registered handles,
* it synchronously de-multiplexes the event which can be any of read, write or accept, and
* dispatches the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}.
* for events from all these handles. Whenever an event occurs on any of the registered handles, it
* synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches
* the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}.
*
* <p>
* Implementation:
* A NIO reactor runs in its own thread when it is started using {@link #start()} method.
* {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing.
* Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()}
* method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing.
*
* <p>
* NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible edge cases
* which are required in a real application. This implementation is meant to demonstrate the fundamental
* concepts that lie behind Reactor pattern.
* NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible
* edge cases which are required in a real application. This implementation is meant to demonstrate
* the fundamental concepts that lie behind Reactor pattern.
*
* @author npathai
*
*/
public class NioReactor {
private Selector selector;
private Dispatcher dispatcher;
private final Selector selector;
private final Dispatcher dispatcher;
/**
* All the work of altering the SelectionKey operations and Selector operations are performed in
* the context of main event loop of reactor. So when any channel needs to change its readability
* or writability, a new command is added in the command queue and then the event loop picks up
* the command and executes it in next iteration.
*/
private Queue<Runnable> pendingCommands = new ConcurrentLinkedQueue<>();
private ExecutorService reactorMain = Executors.newSingleThreadExecutor();
private final Queue<Runnable> pendingCommands = new ConcurrentLinkedQueue<>();
private final ExecutorService reactorMain = Executors.newSingleThreadExecutor();
/**
* Creates a reactor which will use provided {@code dispatcher} to dispatch events.
* The application can provide various implementations of dispatcher which suits its
* needs.
* Creates a reactor which will use provided {@code dispatcher} to dispatch events. The
* application can provide various implementations of dispatcher which suits its needs.
*
* @param dispatcher a non-null dispatcher used to dispatch events on registered channels.
* @throws IOException if any I/O error occurs.
@ -76,22 +75,21 @@ public class NioReactor {
/**
* Stops the reactor and related resources such as dispatcher.
*
* @throws InterruptedException if interrupted while stopping the reactor.
*/
public void stop() {
public void stop() throws InterruptedException {
reactorMain.shutdownNow();
selector.wakeup();
try {
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
dispatcher.stop();
}
/**
* Registers a new channel (handle) with this reactor. Reactor will start waiting for events on this channel
* and notify of any events. While registering the channel the reactor uses {@link AbstractNioChannel#getInterestedOps()}
* to know about the interested operation of this channel.
* Registers a new channel (handle) with this reactor. Reactor will start waiting for events on
* this channel and notify of any events. While registering the channel the reactor uses
* {@link AbstractNioChannel#getInterestedOps()} to know about the interested operation of this
* channel.
*
* @param channel a new channel on which reactor will wait for events. The channel must be bound
* prior to being registered.
@ -117,9 +115,8 @@ public class NioReactor {
processPendingCommands();
/*
* Synchronous event de-multiplexing happens here, this is blocking call which
* returns when it is possible to initiate non-blocking operation on any of the
* registered channels.
* Synchronous event de-multiplexing happens here, this is blocking call which returns when it
* is possible to initiate non-blocking operation on any of the registered channels.
*/
selector.select();
@ -152,8 +149,8 @@ public class NioReactor {
}
/*
* Initiation dispatcher logic, it checks the type of event and notifier application
* specific event handler to handle the event.
* Initiation dispatcher logic, it checks the type of event and notifier application specific
* event handler to handle the event.
*/
private void processKey(SelectionKey key) throws IOException {
if (key.isAcceptable()) {
@ -204,8 +201,8 @@ public class NioReactor {
* Queues the change of operations request of a channel, which will change the interested
* operations of the channel sometime in future.
* <p>
* This is a non-blocking method and does not guarantee that the operations have changed when
* this method returns.
* This is a non-blocking method and does not guarantee that the operations have changed when this
* method returns.
*
* @param key the key for which operations have to be changed.
* @param interestedOps the new interest operations.

View File

@ -9,20 +9,21 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* A wrapper over {@link NioServerSocketChannel} which can read and write data on a {@link SocketChannel}.
* A wrapper over {@link NioServerSocketChannel} which can read and write data on a
* {@link SocketChannel}.
*
* @author npathai
*/
public class NioServerSocketChannel extends AbstractNioChannel {
private int port;
private final int port;
/**
* Creates a {@link ServerSocketChannel} which will bind at provided port and use
* <code>handler</code> to handle incoming events on this channel.
* <p>
* Note the constructor does not bind the socket, {@link #bind()} method should be called for binding
* the socket.
* Note the constructor does not bind the socket, {@link #bind()} method should be called for
* binding the socket.
*
* @param port the port on which channel will be bound to accept incoming connection requests.
* @param handler the handler that will handle incoming requests on this channel.
@ -50,8 +51,8 @@ public class NioServerSocketChannel extends AbstractNioChannel {
/**
* Reads and returns {@link ByteBuffer} from the underlying {@link SocketChannel} represented by
* the <code>key</code>. Due to the fact that there is a dedicated channel for each client connection
* we don't need to store the sender.
* the <code>key</code>. Due to the fact that there is a dedicated channel for each client
* connection we don't need to store the sender.
*/
@Override
public ByteBuffer read(SelectionKey key) throws IOException {
@ -78,8 +79,8 @@ public class NioServerSocketChannel extends AbstractNioChannel {
}
/**
* Writes the pending {@link ByteBuffer} to the underlying channel sending data to
* the intended receiver of the packet.
* Writes the pending {@link ByteBuffer} to the underlying channel sending data to the intended
* receiver of the packet.
*/
@Override
protected void doWrite(Object pendingWrite, SelectionKey key) throws IOException {

View File

@ -4,8 +4,8 @@ import java.nio.channels.SelectionKey;
/**
* Dispatches the events in the context of caller thread. This implementation is a good fit for
* small applications where there are limited clients. Using this implementation limits the scalability
* because the I/O thread performs the application specific processing.
* small applications where there are limited clients. Using this implementation limits the
* scalability because the I/O thread performs the application specific processing.
*
* <p>
* For better performance use {@link ThreadPoolDispatcher}.
@ -17,21 +17,18 @@ import java.nio.channels.SelectionKey;
public class SameThreadDispatcher implements Dispatcher {
/**
* Dispatches the read event in the context of caller thread.
* <br/>
* Note this is a blocking call. It returns only after the associated handler has handled the
* read event.
* Dispatches the read event in the context of caller thread. <br/>
* Note this is a blocking call. It returns only after the associated handler has handled the read
* event.
*/
@Override
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
if (channel.getHandler() != null) {
/*
* Calls the associated handler to notify the read event where application specific code
* resides.
*/
channel.getHandler().handleChannelRead(channel, readObject, key);
}
}
/**
* No resources to free.

View File

@ -6,16 +6,16 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* An implementation that uses a pool of worker threads to dispatch the events. This provides
* better scalability as the application specific processing is not performed in the context
* of I/O (reactor) thread.
* An implementation that uses a pool of worker threads to dispatch the events. This provides better
* scalability as the application specific processing is not performed in the context of I/O
* (reactor) thread.
*
* @author npathai
*
*/
public class ThreadPoolDispatcher extends SameThreadDispatcher {
public class ThreadPoolDispatcher implements Dispatcher {
private ExecutorService executorService;
private final ExecutorService executorService;
/**
* Creates a pooled dispatcher with tunable pool size.
@ -27,29 +27,24 @@ public class ThreadPoolDispatcher extends SameThreadDispatcher {
}
/**
* Submits the work of dispatching the read event to worker pool, where it gets picked
* up by worker threads.
* <br/>
* Note that this is a non-blocking call and returns immediately. It is not guaranteed
* that the event has been handled by associated handler.
* Submits the work of dispatching the read event to worker pool, where it gets picked up by
* worker threads. <br/>
* Note that this is a non-blocking call and returns immediately. It is not guaranteed that the
* event has been handled by associated handler.
*/
@Override
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
executorService.execute(() ->
ThreadPoolDispatcher.super.onChannelReadEvent(channel, readObject, key));
executorService.execute(() -> channel.getHandler().handleChannelRead(channel, readObject, key));
}
/**
* Stops the pool of workers.
*
* @throws InterruptedException if interrupted while stopping pool of workers.
*/
@Override
public void stop() {
executorService.shutdownNow();
try {
public void stop() throws InterruptedException {
executorService.shutdown();
executorService.awaitTermination(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -4,16 +4,30 @@ import java.io.IOException;
import org.junit.Test;
/**
*
* This class tests the Distributed Logging service by starting a Reactor and then sending it
* concurrent logging requests using multiple clients.
*
* @author npathai
*/
public class AppTest {
/**
* Test the application.
*
* @throws IOException if any I/O error occurs.
* @throws InterruptedException if interrupted while stopping the application.
*/
@Test
public void testApp() throws IOException {
public void testApp() throws IOException, InterruptedException {
App app = new App();
app.start();
AppClient client = new AppClient();
client.start();
// allow clients to send requests. Artificial delay.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {