The selectors API in Java is a paradigm that I spent much time understanding during this last months. I developed a small but, I think, powerful library related to this topic, and this post will be followed by several other posts that will introduce this library. But before starting on the gory details I wanted to talk a bit about a pattern that I am using with selectors.
Selectors are best used in a more or less infinite loop, something like this:
Selector selector = Selector.open();
while (true) {
if (selector.select() != 0) {
for (Iterator<SelectionKey> iterator = selector.selectedKeys();
iterator.hasNext(); ) {
SelectionKey key = iterator.next();
// Do something with the key
iterator.remove();
}
}
}
The problem is that the selector is synchronized on itself and on the keys set when blocked in a select. This means that registering a new channel will simply be stuck waiting on a monitor until the select exits. If the selector is used with other channels, and there is some activity on this channels then the register will execute quickly and this bug will not be seen. But if this is the first channel to be registered then there will be a deadlock. One step in the right direction is to force the selector to wakeup just before registering:
selector.wakeup();
channel.register(channel, OP_READ);
Unfortunately, this still does not work. The reason is that nothing prevents the loop to execute the select a second time after the wakeup and before the call to register. It is easy to write a test program to verify this. The solution is to use a synchronization to prevent the loop to reenter the select before the register is executed. The register is inside the synchronized:
Object lock = new Object();
synchronized (lock) {
selector.wakeup();
channel.register(channel, OP_READ);
}
and the select loop now needs an empty synchronized:
Selector selector = Selector.open();
while (true) {
if (selector.select() != 0) {
for (Iterator<SelectionKey> iterator = selector.selectedKeys();
iterator.hasNext(); ) {
SelectionKey key = iterator.next();
// Do something with the key
iterator.remove();
}
}
synchronized (lock) {}
}
There is other cases that would need to use the same pattern, like reading the selectedKeys set or the keys set in a different thread, as both need to be synchronized but the select method already synchronizes on them.
On the other hand, the selector close method does not need to use this pattern.
The interest set of selection keys is another interesting case. In the current implementation, there is no need to use the synchronization pattern shown above, so calling wakeup after changing the interest set is working fine. But the javadoc is clear that an implementation could behave differently:
“In a naive implementation, reading or writing the interest set may block indefinitely if a selection operation is already in progress;”
So always using the pattern above guarantees that the code will work on different Java implementations.
Now the problem could be that in the future a very optimized compiler or runtime decides that the empty synchronized statement is in fact useless, and decide to remove it.