Java tutorial
/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. **********************************************************************************************************************/ package eu.stratosphere.nephele.taskmanager.bytebuffered; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.ArrayDeque; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Queue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import eu.stratosphere.util.StringUtils; public class OutgoingConnectionThread extends Thread { /** * The minimum time a TCP connection must be idle it is closed. */ private static final long MIN_IDLE_TIME_BEFORE_CLOSE = 80000L; // 80 seconds private static final Log LOG = LogFactory.getLog(OutgoingConnectionThread.class); private final Selector selector; private final Queue<OutgoingConnection> pendingConnectionRequests = new ArrayDeque<OutgoingConnection>(); private final Queue<SelectionKey> pendingWriteEventSubscribeRequests = new ArrayDeque<SelectionKey>(); private final Map<OutgoingConnection, Long> connectionsToClose = new HashMap<OutgoingConnection, Long>(); public OutgoingConnectionThread() throws IOException { super("Outgoing Connection Thread"); this.selector = Selector.open(); } @Override public void run() { while (!isInterrupted()) { synchronized (this.pendingConnectionRequests) { if (!this.pendingConnectionRequests.isEmpty()) { final OutgoingConnection outgoingConnection = this.pendingConnectionRequests.poll(); try { final SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); final SelectionKey key = socketChannel.register(this.selector, SelectionKey.OP_CONNECT); socketChannel.connect(outgoingConnection.getConnectionAddress()); key.attach(outgoingConnection); } catch (final IOException ioe) { // IOException is reported by separate thread to avoid deadlocks final Runnable reporterThread = new Runnable() { @Override public void run() { outgoingConnection.reportConnectionProblem(ioe); } }; new Thread(reporterThread).start(); } } } synchronized (this.pendingWriteEventSubscribeRequests) { if (!this.pendingWriteEventSubscribeRequests.isEmpty()) { final SelectionKey oldSelectionKey = this.pendingWriteEventSubscribeRequests.poll(); final OutgoingConnection outgoingConnection = (OutgoingConnection) oldSelectionKey.attachment(); final SocketChannel socketChannel = (SocketChannel) oldSelectionKey.channel(); try { final SelectionKey newSelectionKey = socketChannel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); newSelectionKey.attach(outgoingConnection); outgoingConnection.setSelectionKey(newSelectionKey); } catch (final IOException ioe) { // IOException is reported by separate thread to avoid deadlocks final Runnable reporterThread = new Runnable() { @Override public void run() { outgoingConnection.reportTransmissionProblem(ioe); } }; new Thread(reporterThread).start(); } } } synchronized (this.connectionsToClose) { final Iterator<Map.Entry<OutgoingConnection, Long>> closeIt = this.connectionsToClose.entrySet() .iterator(); final long now = System.currentTimeMillis(); while (closeIt.hasNext()) { final Map.Entry<OutgoingConnection, Long> entry = closeIt.next(); if ((entry.getValue().longValue() + MIN_IDLE_TIME_BEFORE_CLOSE) < now) { final OutgoingConnection outgoingConnection = entry.getKey(); closeIt.remove(); // Create new thread to close connection to avoid deadlocks final Runnable closeThread = new Runnable() { @Override public void run() { try { outgoingConnection.closeConnection(); } catch (IOException ioe) { outgoingConnection.reportTransmissionProblem(ioe); } } }; new Thread(closeThread).start(); } } } try { this.selector.select(10); } catch (IOException e) { LOG.error(e); } final Iterator<SelectionKey> iter = this.selector.selectedKeys().iterator(); while (iter.hasNext()) { final SelectionKey key = iter.next(); iter.remove(); if (key.isValid()) { if (key.isConnectable()) { doConnect(key); } else { if (key.isReadable()) { doRead(key); // A read will always result in an exception, so the write key will not be valid anymore continue; } if (key.isWritable()) { doWrite(key); } } } else { LOG.error("Received invalid key: " + key); } } } // Finally, try to close the selector try { this.selector.close(); } catch (IOException ioe) { LOG.debug(StringUtils.stringifyException(ioe)); } } private void doConnect(SelectionKey key) { final OutgoingConnection outgoingConnection = (OutgoingConnection) key.attachment(); final SocketChannel socketChannel = (SocketChannel) key.channel(); try { while (!socketChannel.finishConnect()) { try { Thread.sleep(100); } catch (InterruptedException e1) { LOG.error(e1); } } final SelectionKey channelKey = socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); outgoingConnection.setSelectionKey(channelKey); channelKey.attach(outgoingConnection); } catch (IOException ioe) { outgoingConnection.reportConnectionProblem(ioe); } } private void doWrite(SelectionKey key) { final OutgoingConnection outgoingConnection = (OutgoingConnection) key.attachment(); try { if (!outgoingConnection.write()) { // Try to close the connection outgoingConnection.requestClose(); } } catch (IOException ioe) { outgoingConnection.reportTransmissionProblem(ioe); } } private void doRead(SelectionKey key) { final SocketChannel socketChannel = (SocketChannel) key.channel(); final OutgoingConnection outgoingConnection = (OutgoingConnection) key.attachment(); final ByteBuffer buf = ByteBuffer.allocate(8); try { if (socketChannel.read(buf) == -1) { outgoingConnection.reportTransmissionProblem(new IOException("Read unexpected EOF from channel")); } else { LOG.error("Outgoing connection read real data from channel"); } } catch (IOException ioe) { outgoingConnection.reportTransmissionProblem(ioe); } } public void triggerConnect(OutgoingConnection outgoingConnection) { synchronized (this.pendingConnectionRequests) { this.pendingConnectionRequests.add(outgoingConnection); } } public void unsubscribeFromWriteEvent(SelectionKey selectionKey) throws IOException { final SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); final OutgoingConnection outgoingConnection = (OutgoingConnection) selectionKey.attachment(); final SelectionKey newSelectionKey = socketChannel.register(this.selector, SelectionKey.OP_READ); newSelectionKey.attach(outgoingConnection); outgoingConnection.setSelectionKey(newSelectionKey); synchronized (this.connectionsToClose) { this.connectionsToClose.put(outgoingConnection, Long.valueOf(System.currentTimeMillis())); } } public void subscribeToWriteEvent(SelectionKey selectionKey) { synchronized (this.pendingWriteEventSubscribeRequests) { this.pendingWriteEventSubscribeRequests.add(selectionKey); } synchronized (this.connectionsToClose) { this.connectionsToClose.remove((OutgoingConnection) selectionKey.attachment()); } } }