All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.stratosphere.runtime.io.network.ChannelManager Maven / Gradle / Ivy

The newest version!
/***********************************************************************************************************************
 * Copyright (C) 2010-2014 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.runtime.io.network;

import eu.stratosphere.core.io.IOReadableWritable;
import eu.stratosphere.nephele.execution.CancelTaskException;
import eu.stratosphere.nephele.execution.Environment;
import eu.stratosphere.nephele.execution.RuntimeEnvironment;
import eu.stratosphere.nephele.executiongraph.ExecutionVertexID;
import eu.stratosphere.nephele.instance.InstanceConnectionInfo;
import eu.stratosphere.runtime.io.channels.Channel;
import eu.stratosphere.runtime.io.channels.ChannelID;
import eu.stratosphere.runtime.io.channels.ChannelType;
import eu.stratosphere.runtime.io.channels.InputChannel;
import eu.stratosphere.runtime.io.channels.OutputChannel;
import eu.stratosphere.nephele.jobgraph.JobID;
import eu.stratosphere.nephele.protocols.ChannelLookupProtocol;
import eu.stratosphere.nephele.taskmanager.Task;
import eu.stratosphere.nephele.AbstractID;
import eu.stratosphere.runtime.io.Buffer;
import eu.stratosphere.runtime.io.network.bufferprovider.BufferProvider;
import eu.stratosphere.runtime.io.network.bufferprovider.BufferProviderBroker;
import eu.stratosphere.runtime.io.network.bufferprovider.DiscardBufferPool;
import eu.stratosphere.runtime.io.network.bufferprovider.GlobalBufferPool;
import eu.stratosphere.runtime.io.network.bufferprovider.LocalBufferPoolOwner;
import eu.stratosphere.runtime.io.gates.GateID;
import eu.stratosphere.runtime.io.gates.InputGate;
import eu.stratosphere.runtime.io.gates.OutputGate;
import eu.stratosphere.runtime.io.network.netty.NettyConnectionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The channel manager sets up the network buffers and dispatches data between channels.
 */
public class ChannelManager implements EnvelopeDispatcher, BufferProviderBroker {

	private static final Log LOG = LogFactory.getLog(ChannelManager.class);

	private final ChannelLookupProtocol channelLookupService;

	private final InstanceConnectionInfo connectionInfo;

	private final Map channels;

	private final Map localBuffersPools;

	private final Map receiverCache;

	private final GlobalBufferPool globalBufferPool;

	private final NettyConnectionManager nettyConnectionManager;
	
	private final InetSocketAddress ourAddress;
	
	private final DiscardBufferPool discardBufferPool;

	// -----------------------------------------------------------------------------------------------------------------

	public ChannelManager(ChannelLookupProtocol channelLookupService, InstanceConnectionInfo connectionInfo,
						int numNetworkBuffers, int networkBufferSize,
						int numInThreads, int numOutThreads,
						int lowWatermark, int highWaterMark) throws IOException {

		this.channelLookupService = channelLookupService;
		this.connectionInfo = connectionInfo;

		this.globalBufferPool = new GlobalBufferPool(numNetworkBuffers, networkBufferSize);

		this.nettyConnectionManager = new NettyConnectionManager(
				this, connectionInfo.address(), connectionInfo.dataPort(),
				networkBufferSize, numInThreads, numOutThreads, lowWatermark, highWaterMark);

		// management data structures
		this.channels = new ConcurrentHashMap();
		this.receiverCache = new ConcurrentHashMap();
		this.localBuffersPools = new ConcurrentHashMap();
		
		this.ourAddress = new InetSocketAddress(connectionInfo.address(), connectionInfo.dataPort());
		
		// a special pool if the data is to be discarded
		this.discardBufferPool = new DiscardBufferPool();
	}

	public void shutdown() {
		this.nettyConnectionManager.shutdown();
		this.globalBufferPool.destroy();
	}

	// -----------------------------------------------------------------------------------------------------------------
	//                                               Task registration
	// -----------------------------------------------------------------------------------------------------------------

	/**
	 * Registers the given task with the channel manager.
	 *
	 * @param task the task to be registered
	 * @throws InsufficientResourcesException thrown if not enough buffers available to safely run this task
	 */
	public void register(Task task) throws InsufficientResourcesException {
		// Check if we can safely run this task with the given buffers
		ensureBufferAvailability(task);

		RuntimeEnvironment environment = task.getRuntimeEnvironment();

		// -------------------------------------------------------------------------------------------------------------
		//                                       Register output channels
		// -------------------------------------------------------------------------------------------------------------

		environment.registerGlobalBufferPool(this.globalBufferPool);

		if (this.localBuffersPools.containsKey(task.getVertexID())) {
			throw new IllegalStateException("Vertex " + task.getVertexID() + " has a previous buffer pool owner");
		}

		for (OutputGate gate : environment.outputGates()) {
			// add receiver list hints
			for (OutputChannel channel : gate.channels()) {
				// register envelope dispatcher with the channel
				channel.registerEnvelopeDispatcher(this);

				switch (channel.getChannelType()) {
					case IN_MEMORY:
						addReceiverListHint(channel.getID(), channel.getConnectedId());
						break;
					case NETWORK:
						addReceiverListHint(channel.getConnectedId(), channel.getID());
						break;
				}

				this.channels.put(channel.getID(), channel);
			}
		}

		this.localBuffersPools.put(task.getVertexID(), environment);

		// -------------------------------------------------------------------------------------------------------------
		//                                       Register input channels
		// -------------------------------------------------------------------------------------------------------------

		// register global
		for (InputGate gate : environment.inputGates()) {
			gate.registerGlobalBufferPool(this.globalBufferPool);

			for (int i = 0; i < gate.getNumberOfInputChannels(); i++) {
				InputChannel channel = gate.getInputChannel(i);
				channel.registerEnvelopeDispatcher(this);

				if (channel.getChannelType() == ChannelType.IN_MEMORY) {
					addReceiverListHint(channel.getID(), channel.getConnectedId());
				}

				this.channels.put(channel.getID(), channel);
			}

			this.localBuffersPools.put(gate.getGateID(), gate);
		}

		// the number of channels per buffers has changed after unregistering the task
		// => redistribute the number of designated buffers of the registered local buffer pools
		redistributeBuffers();
	}

	/**
	 * Unregisters the given task from the channel manager.
	 *
	 * @param vertexId the ID of the task to be unregistered
	 * @param task the task to be unregistered
	 */
	public void unregister(ExecutionVertexID vertexId, Task task) {
		final Environment environment = task.getEnvironment();

		// destroy and remove OUTPUT channels from registered channels and cache
		for (ChannelID id : environment.getOutputChannelIDs()) {
			Channel channel = this.channels.remove(id);
			if (channel != null) {
				channel.destroy();
			}

			this.receiverCache.remove(channel);
		}

		// destroy and remove INPUT channels from registered channels and cache
		for (ChannelID id : environment.getInputChannelIDs()) {
			Channel channel = this.channels.remove(id);
			if (channel != null) {
				channel.destroy();
			}

			this.receiverCache.remove(channel);
		}

		// clear and remove INPUT side buffer pools
		for (GateID id : environment.getInputGateIDs()) {
			LocalBufferPoolOwner bufferPool = this.localBuffersPools.remove(id);
			if (bufferPool != null) {
				bufferPool.clearLocalBufferPool();
			}
		}

		// clear and remove OUTPUT side buffer pool
		LocalBufferPoolOwner bufferPool = this.localBuffersPools.remove(vertexId);
		if (bufferPool != null) {
			bufferPool.clearLocalBufferPool();
		}

		// the number of channels per buffers has changed after unregistering the task
		// => redistribute the number of designated buffers of the registered local buffer pools
		redistributeBuffers();
	}

	/**
	 * Ensures that the channel manager has enough buffers to execute the given task.
	 * 

* If there is less than one buffer per channel available, an InsufficientResourcesException will be thrown, * because of possible deadlocks. With more then one buffer per channel, deadlock-freedom is guaranteed. * * @param task task to be executed * @throws InsufficientResourcesException thrown if not enough buffers available to execute the task */ private void ensureBufferAvailability(Task task) throws InsufficientResourcesException { Environment env = task.getEnvironment(); int numBuffers = this.globalBufferPool.numBuffers(); // existing channels + channels of the task int numChannels = this.channels.size() + env.getNumberOfOutputChannels() + env.getNumberOfInputChannels(); // need at least one buffer per channel if (numBuffers / numChannels < 1) { String msg = String.format("%s has not enough buffers to safely execute %s (%d buffers missing)", this.connectionInfo.hostname(), env.getTaskName(), numChannels - numBuffers); throw new InsufficientResourcesException(msg); } } /** * Redistributes the buffers among the registered buffer pools. This method is called after each task registration * and unregistration. *

* Every registered buffer pool gets buffers according to its number of channels weighted by the current buffer to * channel ratio. */ private void redistributeBuffers() { if (this.localBuffersPools.isEmpty() | this.channels.size() == 0) { return; } int numBuffers = this.globalBufferPool.numBuffers(); int numChannels = this.channels.size(); double buffersPerChannel = numBuffers / (double) numChannels; if (buffersPerChannel < 1.0) { throw new RuntimeException("System has not enough buffers to execute tasks."); } // redistribute number of designated buffers per buffer pool for (LocalBufferPoolOwner bufferPool : this.localBuffersPools.values()) { int numDesignatedBuffers = (int) Math.ceil(buffersPerChannel * bufferPool.getNumberOfChannels()); bufferPool.setDesignatedNumberOfBuffers(numDesignatedBuffers); } } // ----------------------------------------------------------------------------------------------------------------- // Envelope processing // ----------------------------------------------------------------------------------------------------------------- private void releaseEnvelope(Envelope envelope) { Buffer buffer = envelope.getBuffer(); if (buffer != null) { buffer.recycleBuffer(); } } private void addReceiverListHint(ChannelID source, ChannelID localReceiver) { EnvelopeReceiverList receiverList = new EnvelopeReceiverList(localReceiver); if (this.receiverCache.put(source, receiverList) != null) { LOG.warn("Receiver cache already contained entry for " + source); } } private void addReceiverListHint(ChannelID source, RemoteReceiver remoteReceiver) { EnvelopeReceiverList receiverList = new EnvelopeReceiverList(remoteReceiver); if (this.receiverCache.put(source, receiverList) != null) { LOG.warn("Receiver cache already contained entry for " + source); } } private void generateSenderHint(Envelope envelope, RemoteReceiver receiver) throws IOException { Channel channel = this.channels.get(envelope.getSource()); if (channel == null) { LOG.error("Cannot find channel for channel ID " + envelope.getSource()); return; } // Only generate sender hints for output channels if (channel.isInputChannel()) { return; } final ChannelID targetChannelID = channel.getConnectedId(); final int connectionIndex = receiver.getConnectionIndex(); final RemoteReceiver ourAddress = new RemoteReceiver(this.ourAddress, connectionIndex); final Envelope senderHint = SenderHintEvent.createEnvelopeWithEvent(envelope, targetChannelID, ourAddress); this.nettyConnectionManager.enqueue(senderHint, receiver); } /** * Returns the list of receivers for transfer envelopes produced by the channel with the given source channel ID. * * @param jobID * the ID of the job the given channel ID belongs to * @param sourceChannelID * the source channel ID for which the receiver list shall be retrieved * @return the list of receivers or null if the receiver could not be determined * @throws IOException */ private EnvelopeReceiverList getReceiverList(JobID jobID, ChannelID sourceChannelID, boolean reportException) throws IOException { EnvelopeReceiverList receiverList = this.receiverCache.get(sourceChannelID); if (receiverList != null) { return receiverList; } while (true) { ConnectionInfoLookupResponse lookupResponse; synchronized (this.channelLookupService) { lookupResponse = this.channelLookupService.lookupConnectionInfo(this.connectionInfo, jobID, sourceChannelID); } if (lookupResponse.receiverReady()) { receiverList = new EnvelopeReceiverList(lookupResponse); break; } else if (lookupResponse.receiverNotReady()) { try { Thread.sleep(500); } catch (InterruptedException e) { if (reportException) { throw new IOException("Lookup was interrupted."); } else { return null; } } } else if (lookupResponse.isJobAborting()) { if (reportException) { throw new CancelTaskException(); } else { return null; } } else if (lookupResponse.receiverNotFound()) { if (reportException) { throw new IOException("Could not find the receiver for Job " + jobID + ", channel with source id " + sourceChannelID); } else { return null; } } else { throw new IllegalStateException("Unrecognized response to channel lookup."); } } this.receiverCache.put(sourceChannelID, receiverList); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Receiver for %s: %s [%s])", sourceChannelID, receiverList.hasLocalReceiver() ? receiverList.getLocalReceiver() : receiverList.getRemoteReceiver(), receiverList.hasLocalReceiver() ? "local" : "remote")); } return receiverList; } /** * Invalidates the entries identified by the given channel IDs from the receiver lookup cache. * * @param channelIDs channel IDs for entries to invalidate */ public void invalidateLookupCacheEntries(Set channelIDs) { for (ChannelID id : channelIDs) { this.receiverCache.remove(id); } } // ----------------------------------------------------------------------------------------------------------------- // EnvelopeDispatcher methods // ----------------------------------------------------------------------------------------------------------------- @Override public void dispatchFromOutputChannel(Envelope envelope) throws IOException, InterruptedException { EnvelopeReceiverList receiverList = getReceiverListForEnvelope(envelope, true); Buffer srcBuffer = envelope.getBuffer(); Buffer destBuffer = null; boolean success = false; try { if (receiverList.hasLocalReceiver()) { ChannelID receiver = receiverList.getLocalReceiver(); Channel channel = this.channels.get(receiver); if (channel == null) { throw new LocalReceiverCancelledException(receiver); } if (!channel.isInputChannel()) { throw new IOException("Local receiver " + receiver + " is not an input channel."); } InputChannel inputChannel = (InputChannel) channel; // copy the buffer into the memory space of the receiver if (srcBuffer != null) { try { destBuffer = inputChannel.requestBufferBlocking(srcBuffer.size()); } catch (InterruptedException e) { throw new IOException(e.getMessage()); } srcBuffer.copyToBuffer(destBuffer); envelope.setBuffer(destBuffer); srcBuffer.recycleBuffer(); } inputChannel.queueEnvelope(envelope); success = true; } else if (receiverList.hasRemoteReceiver()) { RemoteReceiver remoteReceiver = receiverList.getRemoteReceiver(); // Generate sender hint before sending the first envelope over the network if (envelope.getSequenceNumber() == 0) { generateSenderHint(envelope, remoteReceiver); } this.nettyConnectionManager.enqueue(envelope, remoteReceiver); success = true; } } finally { if (!success) { if (srcBuffer != null) { srcBuffer.recycleBuffer(); } if (destBuffer != null) { destBuffer.recycleBuffer(); } } } } @Override public void dispatchFromInputChannel(Envelope envelope) throws IOException, InterruptedException { // this method sends only events back from input channels to output channels // sanity check that we have no buffer if (envelope.getBuffer() != null) { throw new RuntimeException("Error: This method can only process envelopes without buffers."); } EnvelopeReceiverList receiverList = getReceiverListForEnvelope(envelope, true); if (receiverList.hasLocalReceiver()) { ChannelID receiver = receiverList.getLocalReceiver(); Channel channel = this.channels.get(receiver); if (channel == null) { throw new LocalReceiverCancelledException(receiver); } if (channel.isInputChannel()) { throw new IOException("Local receiver " + receiver + " of backward event is not an output channel."); } OutputChannel outputChannel = (OutputChannel) channel; outputChannel.queueEnvelope(envelope); } else if (receiverList.hasRemoteReceiver()) { RemoteReceiver remoteReceiver = receiverList.getRemoteReceiver(); // Generate sender hint before sending the first envelope over the network if (envelope.getSequenceNumber() == 0) { generateSenderHint(envelope, remoteReceiver); } this.nettyConnectionManager.enqueue(envelope, remoteReceiver); } } /** * */ @Override public void dispatchFromNetwork(Envelope envelope) throws IOException, InterruptedException { // ======================================================================================== // IMPORTANT // // This method is called by the network I/O thread that reads the incoming TCP // connections. This method must have minimal overhead and not throw exception if // something is wrong with a job or individual transmission, but only when something // is fundamentally broken in the system. // ======================================================================================== // the sender hint event is to let the receiver know where exactly the envelope came from. // the receiver will cache the sender id and its connection info in its local lookup table // that allows the receiver to send envelopes to the sender without first pinging the job manager // for the sender's connection info // Check if the envelope is the special envelope with the sender hint event if (SenderHintEvent.isSenderHintEvent(envelope)) { // Check if this is the final destination of the sender hint event before adding it final SenderHintEvent seh = (SenderHintEvent) envelope.deserializeEvents().get(0); if (this.channels.get(seh.getSource()) != null) { addReceiverListHint(seh.getSource(), seh.getRemoteReceiver()); return; } } // try and get the receiver list. if we cannot get it anymore, the task has been cleared // the code frees the envelope on exception, so we need not to anything EnvelopeReceiverList receiverList = getReceiverListForEnvelope(envelope, false); if (receiverList == null) { // receiver is cancelled and cleaned away releaseEnvelope(envelope); if (LOG.isDebugEnabled()) { LOG.debug("Dropping envelope for cleaned up receiver."); } return; } if (!receiverList.hasLocalReceiver() || receiverList.hasRemoteReceiver()) { throw new IOException("Bug in network stack: Envelope dispatched from the incoming network pipe has no local receiver or has a remote receiver"); } ChannelID localReceiver = receiverList.getLocalReceiver(); Channel channel = this.channels.get(localReceiver); // if the channel is null, it means that receiver has been cleared already (cancelled or failed). // release the buffer immediately if (channel == null) { releaseEnvelope(envelope); if (LOG.isDebugEnabled()) { LOG.debug("Dropping envelope for cancelled receiver " + localReceiver); } } else { channel.queueEnvelope(envelope); } } /** * * Upon an exception, this method frees the envelope. * * @param envelope * @return * @throws IOException */ private final EnvelopeReceiverList getReceiverListForEnvelope(Envelope envelope, boolean reportException) throws IOException { try { return getReceiverList(envelope.getJobID(), envelope.getSource(), reportException); } catch (IOException e) { releaseEnvelope(envelope); throw e; } catch (CancelTaskException e) { releaseEnvelope(envelope); throw e; } } // ----------------------------------------------------------------------------------------------------------------- // BufferProviderBroker methods // ----------------------------------------------------------------------------------------------------------------- @Override public BufferProvider getBufferProvider(JobID jobID, ChannelID sourceChannelID) throws IOException { EnvelopeReceiverList receiverList = getReceiverList(jobID, sourceChannelID, false); // check if the receiver is already gone if (receiverList == null) { return this.discardBufferPool; } if (!receiverList.hasLocalReceiver() || receiverList.hasRemoteReceiver()) { throw new IOException("The destination to be looked up is not a single local endpoint."); } ChannelID localReceiver = receiverList.getLocalReceiver(); Channel channel = this.channels.get(localReceiver); if (channel == null) { // receiver is already canceled return this.discardBufferPool; } if (!channel.isInputChannel()) { throw new IOException("Channel context for local receiver " + localReceiver + " is not an input channel context"); } return (InputChannel) channel; } // ----------------------------------------------------------------------------------------------------------------- public void logBufferUtilization() { System.out.println("Buffer utilization at " + System.currentTimeMillis()); System.out.println("\tUnused global buffers: " + this.globalBufferPool.numAvailableBuffers()); System.out.println("\tLocal buffer pool status:"); for (LocalBufferPoolOwner bufferPool : this.localBuffersPools.values()) { bufferPool.logBufferUtilization(); } System.out.println("\tIncoming connections:"); for (Channel channel : this.channels.values()) { if (channel.isInputChannel()) { ((InputChannel) channel).logQueuedEnvelopes(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy