/*
* 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.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 that processes OP_WRITE events for SocketChannels.
*
* @author Christian Lins
* @since sonews/0.5.0
*/
class ChannelWriter extends DaemonRunner {
private static final ChannelWriter instance = new ChannelWriter();
public static ChannelWriter getInstance() {
return instance;
}
private Selector selector = null;
protected ChannelWriter() {
}
public Selector getSelector() {
return this.selector;
}
public void setSelector(final Selector selector) {
this.selector = selector;
}
@Override
public void run() {
assert selector != null;
while (daemon.isRunning()) {
try {
SelectionKey selKey = null;
SocketChannel socketChannel = null;
NNTPConnection connection = null;
// select() blocks until some SelectableChannels are ready for
// processing. There is no need to synchronize the selector as
// we have only one thread per selector.
selector.select(); // The return value of select can be ignored
// Get list of selection keys with pending OP_WRITE events.
// The keySET is not thread-safe whereas the keys itself are.
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// We remove the first event from the set and store it for
// later processing.
selKey = it.next();
socketChannel = (SocketChannel) selKey.channel();
connection = Connections.getInstance().get(
new SocketChannelWrapperFactory(socketChannel).create());
it.remove();
if (connection != null) {
break;
} else {
selKey = null;
}
}
if (selKey != null) {
try {
// Process the selected key.
// As there is only one OP_WRITE key for a given
// channel, we need
// not to synchronize this processing to retain the
// order.
processSelectionKey(connection, socketChannel, selKey);
} catch (IOException ex) {
Log.get().log(Level.WARNING, "Error writing to channel: {0}", ex);
// Cancel write events for this channel
selKey.cancel();
if (connection != null) {
connection.close();
}
}
}
// Eventually wait for a register operation
synchronized (SynchronousNNTPDaemon.RegisterGate) { /* do nothing */
}
} catch (CancelledKeyException ex) {
Log.get().log(Level.INFO, "ChannelWriter.run(): {0}", ex);
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
}
} // while(isRunning())
}
private void processSelectionKey(final NNTPConnection connection,
final SocketChannel socketChannel, final SelectionKey selKey)
throws InterruptedException, IOException {
assert connection != null;
assert socketChannel != null;
assert selKey != null;
assert selKey.isWritable();
// SocketChannel is ready for writing
if (selKey.isValid()) {
// Lock the socket channel
synchronized (socketChannel) {
// Get next output buffer
ByteBuffer buf = connection.getOutputBuffer();
if (buf == null) {
// Currently we have nothing to write, so we stop the
// writeable
// events until we have something to write to the socket
// channel
// selKey.cancel();
selKey.interestOps(0);
// Update activity timestamp to prevent too early
// disconnects
// on slow client connections
connection.setLastActivity(System.currentTimeMillis());
return;
}
while (buf != null) // There is data to be send
{
// Write buffer to socket channel; this method does not
// block
if (socketChannel.write(buf) <= 0) {
// Perhaps there is data to be written, but the
// SocketChannel's buffer is full, so we stop writing
// to until the next event.
break;
} else {
// Retrieve next buffer if available; method may return
// the same
// buffer instance if it still have some bytes remaining
buf = connection.getOutputBuffer();
}
}
}
} else {
Log.get().log(Level.WARNING, "Invalid OP_WRITE key: {0}", selKey);
if (socketChannel.socket().isClosed()) {
connection.close();
socketChannel.close();
Log.get().info("Connection closed.");
}
}
}
}