/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.net; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import de.dal33t.powerfolder.Constants; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.message.Ping; import de.dal33t.powerfolder.util.NamedThreadFactory; import de.dal33t.powerfolder.util.Range; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.WrapperExecutorService; /** * Provides basic IO stuff. * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a> * @version $Revision: 1.5 $ */ public class IOProvider extends PFComponent { private static final Logger log = Logger.getLogger(IOProvider.class .getName()); private static final long CONNECTION_KEEP_ALIVE_TIMOUT_MS = Constants.CONNECTION_KEEP_ALIVE_TIMOUT * 1000L; private static final long TIME_WITHOUT_KEEPALIVE_UNTIL_PING = CONNECTION_KEEP_ALIVE_TIMOUT_MS / 3L; /** * The threadpool executing the basic I/O connections to the nodes. */ private ExecutorService ioThreadPool; /** * The connection handler factory. */ private ConnectionHandlerFactory conHanFactory; /** * Manager of relayed connection */ private RelayedConnectionManager relayedConManager; /** * Manager of UDT socket connections */ private UDTSocketConnectionManager udtConManager; /** * The list of connection handlers to check for keepalive */ private List<ConnectionHandler> keepAliveList; private boolean started; public IOProvider(Controller controller) { super(controller); // Create default connection factory. not set this in conHanFactory = new ConnectionHandlerFactory(controller); keepAliveList = new CopyOnWriteArrayList<ConnectionHandler>(); relayedConManager = new RelayedConnectionManager(controller); udtConManager = new UDTSocketConnectionManager(controller, Range.getRangeByNumbers(1024, 65535)); } public void start() { // For basic IO ioThreadPool = new WrapperExecutorService( Executors.newCachedThreadPool(new NamedThreadFactory("IOThread-"))); started = true; getController().scheduleAndRepeat(new KeepAliveChecker(), TIME_WITHOUT_KEEPALIVE_UNTIL_PING); relayedConManager.start(); } public void shutdown() { started = false; if (ioThreadPool != null) { logFine("Shutting down connection I/O threadpool"); ioThreadPool.shutdownNow(); } } /** * Sets the connection handler factory, which is responsible for creating * connection handler for basic io. * * @param conHanFactory * the new factory. */ public synchronized void setConnectionHandlerFactory( ConnectionHandlerFactory conHanFactory) { Reject.ifNull(conHanFactory, "The factory must not be null"); logFiner("Setting new connection factory: " + conHanFactory); this.conHanFactory = conHanFactory; } /** * @return the connection handler factory to create connection handler with. */ public ConnectionHandlerFactory getConnectionHandlerFactory() { return conHanFactory; } /** * @return the relayed connection manager. */ public RelayedConnectionManager getRelayedConnectionManager() { return relayedConManager; } public UDTSocketConnectionManager getUDTSocketConnectionManager() { return udtConManager; } /** * Starts a general connection handling working. * * @param ioWorker * a io worker */ public void startIO(final Runnable ioWorker) { Reject.ifNull(ioWorker, "IO Worker is null"); if (ioThreadPool.isTerminated() || ioThreadPool.isShutdown()) { logFine("Rejected executing of ioWorker, already stopped: " + ioWorker); return; } if (isFiner()) { logFiner("Starting IO for " + ioWorker); } ioThreadPool.submit(ioWorker); } /** * Adds this connection handler to get checked for keepalive. If the * connection handler times out is gets shut down. * * @param conHan * the connection handler to check */ public void startKeepAliveCheck(ConnectionHandler conHan) { Reject.ifNull(conHan, "Connection handler is null"); if (!conHan.isConnected()) { return; } keepAliveList.add(conHan); } /** * Removes this connection handler to get checked for keepalive. * * @param conHan * the connection handler to remove */ public void removeKeepAliveCheck(ConnectionHandler conHan) { Reject.ifNull(conHan, "Connection handler is null"); keepAliveList.remove(conHan); } private class KeepAliveChecker implements Runnable { public void run() { if (!started) { return; } if (log.isLoggable(Level.FINE)) { logFine("Checking " + keepAliveList.size() + " con handlers for keepalive"); } Collection<ConnectionHandler> list = new HashSet<ConnectionHandler>( keepAliveList); if (getController().getNodeManager() != null) { Collection<Member> nodes = getController().getNodeManager() .getNodesAsCollection(); // Might happen on startup if (nodes != null) { for (Member node : nodes) { ConnectionHandler peer = node.getPeer(); if (peer == null) { continue; } if (!peer.isConnected()) { continue; } if (!list.contains(peer)) { logFine("ConHan not in keepalive list of " + node); list.add(peer); } } } } for (ConnectionHandler conHan : list) { if (!conHan.isConnected()) { keepAliveList.remove(conHan); } if (!checkIfOk(conHan)) { keepAliveList.remove(conHan); } } } private boolean checkIfOk(ConnectionHandler conHan) { boolean newPing; Date lastKeepaliveMessage = conHan.getLastKeepaliveMessageTime(); if (lastKeepaliveMessage == null) { newPing = true; } else { long timeWithoutKeepalive = System.currentTimeMillis() - lastKeepaliveMessage.getTime(); newPing = timeWithoutKeepalive >= TIME_WITHOUT_KEEPALIVE_UNTIL_PING; if (isFiner()) { logFiner("Keep-alive check. Received last keep alive message " + timeWithoutKeepalive + "ms ago, ping required? " + newPing + ". Node: " + conHan.getMember()); } if (timeWithoutKeepalive > CONNECTION_KEEP_ALIVE_TIMOUT_MS) { logFine("Shutting down. Dead connection detected (" + (timeWithoutKeepalive / 1000) + "s timeout) to " + conHan.getMember()); conHan.shutdownWithMember(); return false; } } if (newPing) { // Send new ping conHan.sendMessagesAsynchron(new Ping(-1)); } return true; } } }