tom

Starting work on I/O loop.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onlab-nio</artifactId>
<packaging>bundle</packaging>
<description>Fast network I/O using Java NIO</description>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package org.onlab.nio;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Selector loop derivative tailored to acceptConnection inbound connections.
*/
public abstract class AcceptorLoop extends SelectorLoop {
private SocketAddress listenAddress;
private ServerSocketChannel socketChannel;
/**
* Creates an acceptor loop with the specified selection timeout and
* accepting connections on the the given address.
*
* @param selectTimeout selection timeout; specified in millis
* @param listenAddress socket address where to listen for connections
* @throws IOException if the backing selector cannot be opened
*/
public AcceptorLoop(long selectTimeout, SocketAddress listenAddress)
throws IOException {
super(selectTimeout);
this.listenAddress = checkNotNull(this.listenAddress, "Address cannot be null");
}
/**
* Hook to accept an inbound connection on the specified socket channel.
*
* @param channel socketChannel where an accept operation awaits
* @throws IOException if the accept operation cannot be processed
*/
protected abstract void acceptConnection(ServerSocketChannel channel) throws IOException;
/**
* Opens a new server socket channel configured in non-blocking mode and
* bound to the loop's listen address.
*
* @throws IOException if unable to open or configure the socket channel
*/
protected synchronized void openChannel() throws IOException {
socketChannel = ServerSocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
socketChannel.bind(listenAddress);
}
/**
* Closes the server socket channel.
*
* @throws IOException if unable to close the socketChannel
*/
protected synchronized void closechannel() throws IOException {
if (socketChannel != null) {
socketChannel.close();
socketChannel = null;
}
}
@Override
public void shutdown() {
try {
closechannel();
} catch (IOException e) {
log.warn("Unable to close the socketChannel", e);
}
super.shutdown();
}
@Override
protected void loop() throws IOException {
openChannel();
notifyReady();
// Keep looping until told otherwise.
while (isRunning()) {
// Attempt a selection; if no operations selected or if signalled
// to shutdown, spin through.
int count = selector.select(selectTimeout);
if (count == 0 || !isRunning()) {
continue;
}
// Iterate over all keys selected for an operation and process them.
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
// Fetch the key and remove it from the pending list.
SelectionKey key = keys.next();
keys.remove();
// If the key has a pending acceptConnection operation, process it.
if (key.isAcceptable()) {
acceptConnection((ServerSocketChannel) key.channel());
}
}
}
}
}
package org.onlab.nio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.channels.Selector;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Abstraction of an I/O processing loop based on an NIO selector.
*/
public abstract class SelectorLoop implements Runnable {
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* Selector used by this loop to pace the I/O operations.
*/
protected final Selector selector;
/**
* Selection operations timeout; specified in millis.
*/
protected long selectTimeout;
/**
* Retains the error that caused the loop to exit prematurely.
*/
private Throwable error;
// State indicator
private enum State { STARTING, STARTED, STOPPING, STOPPED };
private volatile State state = State.STOPPED;
/**
* Creates a new selector loop with the given selection timeout.
*
* @param selectTimeout selection timeout; specified in millis
* @throws IOException if the backing selector cannot be opened
*/
public SelectorLoop(long selectTimeout) throws IOException {
checkArgument(selectTimeout > 0, "Timeout must be positive");
this.selectTimeout = selectTimeout;
this.selector = openSelector();
}
/**
* Opens a new selector for the use by the loop.
*
* @return newly open selector
* @throws IOException if the backing selector cannot be opened
*/
protected Selector openSelector() throws IOException {
return Selector.open();
}
/**
* Indicates that the loop is marked to run.
*/
protected boolean isRunning() {
return state == State.STARTED || state == State.STARTING;
}
/**
* Returns the error, if there was one, that caused the loop to terminate
* prematurely.
*
* @return error or null if there was none
*/
public Throwable getError() {
return error;
}
/**
* Contains the body of the I/O selector loop.
*
* @throws IOException if an error is encountered while selecting I/O
*/
protected abstract void loop() throws IOException;
@Override
public void run() {
error = null;
state = State.STARTING;
try {
loop();
} catch (Throwable e) {
error = e;
log.error("Loop aborted", e);
}
notifyDone();
}
/**
* Notifies observers waiting for loop to become ready.
*/
protected synchronized void notifyReady() {
state = State.STARTED;
notifyAll();
}
/**
* Triggers loop shutdown.
*/
public void shutdown() {
// Mark the loop as no longer running and wake up the selector.
state = State.STOPPING;
selector.wakeup();
}
/**
* Notifies observers waiting for loop to fully stop.
*/
private synchronized void notifyDone() {
state = State.STOPPED;
notifyAll();
}
}
......@@ -19,6 +19,7 @@
<modules>
<module>junit</module>
<module>misc</module>
<module>nio</module>
<module>osgi</module>
<module>rest</module>
</modules>
......