blob: 4f3220d601ded12a855896632039b68aa58f49d5 [file] [log] [blame]
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import static java.lang.System.currentTimeMillis;
/**
* 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();
}
/**
* Waits for the loop execution to start.
*
* @param timeout number of milliseconds to wait
* @return true if loop started in time
*/
public final synchronized boolean awaitStart(long timeout) {
long max = currentTimeMillis() + timeout;
while (state != State.STARTED && (currentTimeMillis() < max)) {
try {
wait(timeout);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
}
return state == State.STARTED;
}
/**
* Waits for the loop execution to stop.
*
* @param timeout number of milliseconds to wait
* @return true if loop finished in time
*/
public final synchronized boolean awaitStop(long timeout) {
long max = currentTimeMillis() + timeout;
while (state != State.STOPPED && (currentTimeMillis() < max)) {
try {
wait(timeout);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
}
return state == State.STOPPED;
}
}