/*
* 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.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import org.sonews.config.Config;
import org.sonews.daemon.Connections;
import org.sonews.daemon.DaemonRunner;
import org.sonews.daemon.DaemonThread;
import org.sonews.daemon.NNTPConnection;
import org.sonews.daemon.NNTPDaemonRunnable;
import org.sonews.daemon.SocketChannelWrapperFactory;
import org.sonews.util.Log;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* NNTP daemon using SelectableChannels.
*
* @author Christian Lins
* @since sonews/0.5.0
*/
@Component
public class SynchronousNNTPDaemon extends DaemonRunner implements NNTPDaemonRunnable {
public static final Object RegisterGate = new Object();
@Autowired
private ApplicationContext context;
private int port;
private ServerSocket serverSocket = null;
public SynchronousNNTPDaemon() {
}
@Override
public void setPort(int port) {
this.port = port;
}
@Override
public void run() {
try {
Log.get().log(Level.INFO, "Server listening on port {0}", port);
// Create a Selector that handles the SocketChannel multiplexing
final Selector readSelector = Selector.open();
final Selector writeSelector = Selector.open();
// Start working threads
final int workerThreads = Math.max(4, 2 *
Runtime.getRuntime().availableProcessors());
DaemonThread[] cworkers = new DaemonThread[workerThreads];
for (int n = 0; n < workerThreads; n++) {
cworkers[n] = new DaemonThread(new ConnectionWorker());
cworkers[n].start();
}
Log.get().log(Level.INFO, "{0} worker threads started.", workerThreads);
ChannelWriter.getInstance().setSelector(writeSelector);
ChannelReader.getInstance().setSelector(readSelector);
new DaemonThread(ChannelWriter.getInstance()).start();
new DaemonThread(ChannelReader.getInstance()).start();
final ServerSocketChannel serverSocketChannel = ServerSocketChannel
.open();
serverSocketChannel.configureBlocking(true); // Set to blocking mode
// Configure ServerSocket; bind to socket...
serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(this.port));
while (daemon.isRunning()) {
SocketChannel socketChannel;
try {
// As we set the server socket channel to blocking mode the
// accept()
// method will block.
socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
assert socketChannel.isConnected();
assert socketChannel.finishConnect();
} catch (IOException ex) {
// Under heavy load an IOException "Too many open files may
// be thrown. It most cases we should slow down the
// connection accepting, to give the worker threads some
// time to process work.
Log.get().log(
Level.SEVERE, "IOException while accepting connection: {0}", ex.getMessage());
Log.get().info(
"Connection accepting sleeping for seconds...");
Thread.sleep(5000); // 5 seconds
continue;
}
//FIXME conn should be NNTPConnection
final SynchronousNNTPConnection conn = (SynchronousNNTPConnection)
context.getBean("syncNNTPConnection", NNTPConnection.class);
conn.setChannelWrapper(new SocketChannelWrapperFactory(socketChannel).create());
Connections.getInstance().add(conn);
try {
SelectionKey selKeyWrite = registerSelector(writeSelector,
socketChannel, SelectionKey.OP_WRITE);
registerSelector(readSelector, socketChannel,
SelectionKey.OP_READ);
Log.get().log(
Level.INFO, "Connected: {0}", socketChannel.socket()
.getRemoteSocketAddress());
// Set write selection key and send hello to client
conn.setWriteSelectionKey(selKeyWrite);
conn.println("200 "
+ Config.inst().get(Config.HOSTNAME, "localhost")
+ " <unknown version>" // + Application.VERSION
+ " news server ready - (posting ok).");
} catch (CancelledKeyException cke) {
Log.get().log(
Level.WARNING, "CancelledKeyException {0} was thrown: {1}",
new Object[]{cke.getMessage(), socketChannel.socket()});
} catch (ClosedChannelException cce) {
Log.get().log(
Level.WARNING, "ClosedChannelException {0} was thrown: {1}",
new Object[]{cce.getMessage(), socketChannel.socket()});
}
}
} catch (BindException ex) {
// Could not bind to socket; this is a fatal, so perform a shutdown
Log.get().log(Level.SEVERE, ex.getLocalizedMessage() + " -> shutdown sonews", ex);
daemon.requestShutdown();
} catch (IOException | InterruptedException | BeansException ex) {
ex.printStackTrace();
}
}
@Override
public void dispose() {
if (this.serverSocket != null) {
try {
this.serverSocket.close();
} catch (IOException ex) {
Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
}
}
public static SelectionKey registerSelector(final Selector selector,
final SocketChannel channel, final int op)
throws CancelledKeyException, ClosedChannelException {
// Register the selector at the channel, so that it will be notified
// on the socket's events
synchronized (RegisterGate) {
// Wakeup the currently blocking reader/writer thread; we have
// locked the RegisterGate to prevent the awakened thread to block again
selector.wakeup();
// Lock the selector to prevent the waiting worker threads going
// into selector.select() which would block the selector.
synchronized (selector) {
return channel.register(selector, op, null);
}
}
}
}