patternjavaMinor
Event Loop/Select Loop in Java
Viewed 0 times
selectloopjavaevent
Problem
I've been working on a non-blocking IO library, and wanted to get some feedback on my generic event loop class. The goal is that this class will manage select() calls as well as other "events" which must occur on the same thread that the IO is processing.
I'm hoping that this can scale to a relatively large size of simultaneous connections, though I don't actually have a target metric as I just am over-engineering a simple telnet library ;-)
The complete source, including unit tests, is available on github net.virtualinfinity.nio
```
package net.virtualinfinity.nio;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Provides the functionality of an event loop that can listen to {@link SelectableChannel}, as well as execute events
* at some point in the future.
*
* The { @link EventLoop#run() } method does the work. This class is thread safe.
*
* @author Daniel Pitts
*/
public class EventLoop implements Closeable {
private final Selector selector;
private final ExceptionHandler handler;
private final Queue events = new PriorityQueue<>();
private volatile boolean running;
private EventLoop(Selector selector, ExceptionHandler handler) {
this.selector = selector;
this.handler = handler == null ? (key, e) -> { throw e; } : handler;
}
/**
* Creates an EventLoop with the given exception handler. If the handler is null, the default exception handler is
* used, which will re-through the exception. This is generally not the best behavior, and a more suitable exception
* handler should be installed that is specific to your use.
*
* @param handler the exception handler, or null to use the default handler.
*
* @throws IOException if there is an error
I'm hoping that this can scale to a relatively large size of simultaneous connections, though I don't actually have a target metric as I just am over-engineering a simple telnet library ;-)
The complete source, including unit tests, is available on github net.virtualinfinity.nio
```
package net.virtualinfinity.nio;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Provides the functionality of an event loop that can listen to {@link SelectableChannel}, as well as execute events
* at some point in the future.
*
* The { @link EventLoop#run() } method does the work. This class is thread safe.
*
* @author Daniel Pitts
*/
public class EventLoop implements Closeable {
private final Selector selector;
private final ExceptionHandler handler;
private final Queue events = new PriorityQueue<>();
private volatile boolean running;
private EventLoop(Selector selector, ExceptionHandler handler) {
this.selector = selector;
this.handler = handler == null ? (key, e) -> { throw e; } : handler;
}
/**
* Creates an EventLoop with the given exception handler. If the handler is null, the default exception handler is
* used, which will re-through the exception. This is generally not the best behavior, and a more suitable exception
* handler should be installed that is specific to your use.
*
* @param handler the exception handler, or null to use the default handler.
*
* @throws IOException if there is an error
Solution
Just some small nitpicks:
The second line is only two-spaced. Make sure that your spacing is consistent through your code.
Don't import the whole package:
Even though it saves a lot of lines and typing, it's at the cost of performance and readability. Import each class individually.
Also, I really don't like the sight of this:
The conventions say that each line is a maximum of 80 characters. You exceed that many times, especially because of a large amount of arguments to a method, or calling a method with a lot of arguments, or maybe some other reason. After formatting:
```
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
/**
* Provides the functionality of an event loop that can listen to
* {@link SelectableChannel}, as well as execute events at some point in the
* future.
*
* The { @link EventLoop#run() } method does the work. This class is thread
* safe.
*
* @author Daniel Pitts
*/
public class EventLoop implements Closeable {
private final Selector selector;
private final ExceptionHandler handler;
private final Queue events = new PriorityQueue<>();
private volatile boolean running;
private EventLoop(Selector selector, ExceptionHandler handler) {
this.selector = selector;
this.handler = handler == null ? (key, e) -> {
throw e;
} : handler;
}
/**
* Creates an EventLoop with the given exception handler. If the handler is
* null, the default exception handler is used, which will re-through the
* exception. This is generally not the best behavior, and a more suitable
* exception handler should be installed that is specific to your use.
*
* @param handler
* the exception handler, or null to use the default handler.
*
* @throws IOException
* if there is an error opening a selector.
*/
public EventLoop(ExceptionHandler handler) throws IOException {
this(Selector.open(), handler);
}
/**
* Creates an EventLoop instance with the default exception handler.
*
* @throws IOException
* if there is an error opening a selector.
*
* @see #EventLoop(ExceptionHandler)
*/
public EventLoop() throws IOException {
this(Selector.open(), null);
}
/**
* Runs the event loop, dispatching events and listening to
* {@link SelectableChannel}
*
* @throws IOException
*/
public void run() throws IOException {
synchronized (this) {
if (running) {
throw new IllegalStateException(
"Event loop is already running, and is not thread safe");
}
running = true;
}
try {
while (running) {
running = doSelect(timeout(executePendingEvents()));
}
} finally {
synchronized (this) {
running = false;
}
}
}
private boolean doSelect(long timeout) throws IOException {
if (!selector.isOpen()) {
return false;
}
select(timeout);
executeSelected();
return selector.isOpen();
}
private long timeout(Event nextEvent) {
return nextEvent != null ? nextEvent
.timeRemaining(TimeUnit.MILLISECONDS) : 0;
}
/**
* Calls select on the selector, delegating exception management to the
* exception handler.
*
* @param timeout
* the timeout parameter to the {@link Selector#select(long)}
* call.
*
* @throws IOException
* if there is an exception thrown by the exception handler.
*/
private void select(long timeout) throws IOException {
try {
selector.select(timeout);
} catch (IOException e) {
handler.handleException(null, e);
}
}
/**
* Loops through all the selected keys, and executes there Runnable or
* selected methods. This method delegates exception management to the
* exception handler.
*
* @throws IOException
* if there is an exception thrown by the exceptio
public void registerHandler(SelectableChannel channel, SelectionKeyActions handlers) throws ClosedChannelException {
handlers.setSelectionKey(doRegister(channel, handlers.interestOps(), handlers));
}The second line is only two-spaced. Make sure that your spacing is consistent through your code.
Don't import the whole package:
import java.util.*;Even though it saves a lot of lines and typing, it's at the cost of performance and readability. Import each class individually.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;Also, I really don't like the sight of this:
The conventions say that each line is a maximum of 80 characters. You exceed that many times, especially because of a large amount of arguments to a method, or calling a method with a lot of arguments, or maybe some other reason. After formatting:
```
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
/**
* Provides the functionality of an event loop that can listen to
* {@link SelectableChannel}, as well as execute events at some point in the
* future.
*
* The { @link EventLoop#run() } method does the work. This class is thread
* safe.
*
* @author Daniel Pitts
*/
public class EventLoop implements Closeable {
private final Selector selector;
private final ExceptionHandler handler;
private final Queue events = new PriorityQueue<>();
private volatile boolean running;
private EventLoop(Selector selector, ExceptionHandler handler) {
this.selector = selector;
this.handler = handler == null ? (key, e) -> {
throw e;
} : handler;
}
/**
* Creates an EventLoop with the given exception handler. If the handler is
* null, the default exception handler is used, which will re-through the
* exception. This is generally not the best behavior, and a more suitable
* exception handler should be installed that is specific to your use.
*
* @param handler
* the exception handler, or null to use the default handler.
*
* @throws IOException
* if there is an error opening a selector.
*/
public EventLoop(ExceptionHandler handler) throws IOException {
this(Selector.open(), handler);
}
/**
* Creates an EventLoop instance with the default exception handler.
*
* @throws IOException
* if there is an error opening a selector.
*
* @see #EventLoop(ExceptionHandler)
*/
public EventLoop() throws IOException {
this(Selector.open(), null);
}
/**
* Runs the event loop, dispatching events and listening to
* {@link SelectableChannel}
*
* @throws IOException
*/
public void run() throws IOException {
synchronized (this) {
if (running) {
throw new IllegalStateException(
"Event loop is already running, and is not thread safe");
}
running = true;
}
try {
while (running) {
running = doSelect(timeout(executePendingEvents()));
}
} finally {
synchronized (this) {
running = false;
}
}
}
private boolean doSelect(long timeout) throws IOException {
if (!selector.isOpen()) {
return false;
}
select(timeout);
executeSelected();
return selector.isOpen();
}
private long timeout(Event nextEvent) {
return nextEvent != null ? nextEvent
.timeRemaining(TimeUnit.MILLISECONDS) : 0;
}
/**
* Calls select on the selector, delegating exception management to the
* exception handler.
*
* @param timeout
* the timeout parameter to the {@link Selector#select(long)}
* call.
*
* @throws IOException
* if there is an exception thrown by the exception handler.
*/
private void select(long timeout) throws IOException {
try {
selector.select(timeout);
} catch (IOException e) {
handler.handleException(null, e);
}
}
/**
* Loops through all the selected keys, and executes there Runnable or
* selected methods. This method delegates exception management to the
* exception handler.
*
* @throws IOException
* if there is an exception thrown by the exceptio
Code Snippets
public void registerHandler(SelectableChannel channel, SelectionKeyActions handlers) throws ClosedChannelException {
handlers.setSelectionKey(doRegister(channel, handlers.interestOps(), handlers));
}import java.util.*;import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
/**
* Provides the functionality of an event loop that can listen to
* {@link SelectableChannel}, as well as execute events at some point in the
* future.
*
* The { @link EventLoop#run() } method does the work. This class is thread
* safe.
*
* @author <a href='mailto:Daniel@coloraura.com'>Daniel Pitts</a>
*/
public class EventLoop implements Closeable {
private final Selector selector;
private final ExceptionHandler<IOException> handler;
private final Queue<Event> events = new PriorityQueue<>();
private volatile boolean running;
private EventLoop(Selector selector, ExceptionHandler<IOException> handler) {
this.selector = selector;
this.handler = handler == null ? (key, e) -> {
throw e;
} : handler;
}
/**
* Creates an EventLoop with the given exception handler. If the handler is
* null, the default exception handler is used, which will re-through the
* exception. This is generally not the best behavior, and a more suitable
* exception handler should be installed that is specific to your use.
*
* @param handler
* the exception handler, or null to use the default handler.
*
* @throws IOException
* if there is an error opening a selector.
*/
public EventLoop(ExceptionHandler<IOException> handler) throws IOException {
this(Selector.open(), handler);
}
/**
* Creates an EventLoop instance with the default exception handler.
*
* @throws IOException
* if there is an error opening a selector.
*
* @see #EventLoop(ExceptionHandler)
*/
public EventLoop() throws IOException {
this(Selector.open(), null);
}
/**
* Runs the event loop, dispatching events and listening to
* {@link SelectableChannel}
*
* @throws IOException
*/
public void run() throws IOException {
synchronized (this) {
if (running) {
throw new IllegalStateException(
"Event loop is already running, and is not thread safe");
}
running = true;
}
try {
while (running) {
running = doSelect(timeout(executePendingEvents()));
}
} finally {
synchronized (this) {
running = false;
}
}
}
private boolean doSelect(long timeout) throws IOException {
if (!selector.isOpen()) {
return false;
}
selContext
StackExchange Code Review Q#93610, answer score: 3
Revisions (0)
No revisions yet.