/*
* SONEWS News Server
* Copyright (C) 2009-2015 Christian Lins <christian@lins.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sonews.daemon.sync;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import org.sonews.daemon.Connections;
import org.sonews.daemon.DaemonRunner;
import org.sonews.daemon.NNTPConnection;
import org.sonews.daemon.SocketChannelWrapperFactory;
import org.sonews.util.Log;
/**
* A Thread task listening for OP_READ events from SocketChannels.
*
* @author Christian Lins
* @since sonews/0.5.0
*/
class ChannelReader extends DaemonRunner {
private static final ChannelReader instance = new ChannelReader();
/**
* @return Active ChannelReader instance.
*/
public static ChannelReader getInstance() {
return instance;
}
private Selector selector = null;
protected ChannelReader() {
}
/**
* Sets the selector which is used by this reader to determine the channel
* to read from.
*
* @param selector
*/
public void setSelector(final Selector selector) {
this.selector = selector;
}
/**
* Run loop. Blocks until some data is available in a channel.
*/
@Override
public void run() {
assert selector != null;
while (daemon.isRunning()) {
try {
// select() blocks until some SelectableChannels are ready for
// processing. There is no need to lock the selector as we have
// only
// one thread per selector.
selector.select();
// Get list of selection keys with pending events.
// Note: the selected key set is not thread-safe
SocketChannel channel = null;
NNTPConnection conn = null;
final Set<SelectionKey> selKeys = selector.selectedKeys();
SelectionKey selKey = null;
synchronized (selKeys) {
Iterator<SelectionKey> it = selKeys.iterator();
// Process the first pending event
while (it.hasNext()) {
selKey = it.next();
channel = (SocketChannel) selKey.channel();
conn = Connections.getInstance().get(
new SocketChannelWrapperFactory(channel).create());
// Because we cannot lock the selKey as that would cause
// a deadlock
// we lock the connection. To preserve the order of the
// received
// byte blocks a selection key for a connection that has
// pending
// read events is skipped.
if (conn == null || conn.tryReadLock()) {
// Remove from set to indicate that it's being
// processed
it.remove();
if (conn != null) {
break; // End while loop
}
} else {
selKey = null;
channel = null;
conn = null;
}
}
}
// Do not lock the selKeys while processing because this causes
// a deadlock in sun.nio.ch.SelectorImpl.lockAndDoSelect()
if (selKey != null && channel != null && conn != null) {
processSelectionKey(conn, channel, selKey);
conn.unlockReadLock();
}
} catch (CancelledKeyException ex) {
Log.get().log(Level.WARNING, "ChannelReader.run(): {0}", ex);
Log.get().log(Level.INFO, "", ex);
} catch (IOException | InterruptedException ex) {
Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
// Eventually wait for a register operation
synchronized (SynchronousNNTPDaemon.RegisterGate) {
// Do nothing; FindBugs may warn about an empty synchronized
// statement, but we cannot use a wait()/notify() mechanism
// here.
// If we used something like RegisterGate.wait() we block here
// until the NNTPDaemon calls notify(). But the daemon only
// calls notify() if itself is NOT blocked in the listening
// socket.
}
} // while(isRunning())
}
private void processSelectionKey(final NNTPConnection connection,
final SocketChannel socketChannel, final SelectionKey selKey)
throws InterruptedException, IOException {
assert selKey != null;
assert selKey.isReadable();
// Some bytes are available for reading
if (selKey.isValid()) {
// Lock the channel
// synchronized(socketChannel)
{
// Read the data into the appropriate buffer
ByteBuffer buf = connection.getInputBuffer();
int read = -1;
try {
read = socketChannel.read(buf);
} catch (IOException ex) {
// The connection was probably closed by the remote host
// in a non-clean fashion
Log.get().log(Level.INFO,
"ChannelReader.processSelectionKey(): {0}", ex);
} catch (Exception ex) {
Log.get().log(Level.WARNING,
"ChannelReader.processSelectionKey(): {0}", ex);
}
if (read == -1) { // End of stream
selKey.cancel();
} else if (read > 0) { // If some data was read
ConnectionWorker.addChannel(socketChannel);
}
}
} else {
// Should not happen
Log.get().log(Level.SEVERE, "Should not happen: {0}", selKey.toString());
}
}
}