eu.stratosphere.runtime.io.channels.InputChannel.java Source code

Java tutorial

Introduction

Here is the source code for eu.stratosphere.runtime.io.channels.InputChannel.java

Source

/***********************************************************************************************************************
 * 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.runtime.io.channels;

import eu.stratosphere.core.io.IOReadableWritable;
import eu.stratosphere.nephele.event.task.AbstractEvent;
import eu.stratosphere.nephele.event.task.AbstractTaskEvent;
import eu.stratosphere.nephele.jobgraph.JobID;
import eu.stratosphere.runtime.io.Buffer;
import eu.stratosphere.runtime.io.gates.InputChannelResult;
import eu.stratosphere.runtime.io.network.bufferprovider.BufferAvailabilityListener;
import eu.stratosphere.runtime.io.network.bufferprovider.BufferProvider;
import eu.stratosphere.runtime.io.network.Envelope;
import eu.stratosphere.runtime.io.gates.InputGate;
import eu.stratosphere.runtime.io.serialization.AdaptiveSpanningRecordDeserializer;
import eu.stratosphere.runtime.io.serialization.RecordDeserializer;
import eu.stratosphere.runtime.io.serialization.RecordDeserializer.DeserializationResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;

/**
 * InputChannel is an abstract base class to all different kinds of concrete
 * input channels that can be used. Input channels are always parameterized to
 * a specific type that can be transported through the channel.
    
 * @param <T> The Type of the record that can be transported through the channel.
 */
public class InputChannel<T extends IOReadableWritable> extends Channel implements BufferProvider {

    private final InputGate<T> inputGate;

    /**
     * The log object used to report warnings and errors.
     */
    private static final Log LOG = LogFactory.getLog(InputChannel.class);

    /**
     * The deserializer used to deserialize records.
     */
    private final RecordDeserializer<T> deserializer;

    /**
     * Buffer for the uncompressed (raw) data.
     */
    private Buffer dataBuffer;

    private AbstractTaskEvent currentEvent;

    /**
     * The exception observed in this channel while processing the buffers. Checked and thrown
     * per-buffer.
     */
    private volatile IOException ioException;

    /**
     * Stores the number of bytes read through this input channel since its instantiation.
     */
    private long amountOfDataTransmitted;

    private volatile boolean brokerAggreedToCloseChannel;

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

    private int lastReceivedEnvelope = -1;

    private ChannelID lastSourceID = null;

    private boolean destroyCalled = false;

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

    private Queue<Envelope> queuedEnvelopes = new ArrayDeque<Envelope>();

    private Iterator<AbstractEvent> pendingEvents;

    /**
     * Constructs an input channel with a given input gate associated.
     * 
     * @param inputGate
     *        the input gate this channel is connected to
     * @param channelIndex
     *        the index of the channel in the input gate
     * @param channelID
     *        the ID of the channel
     * @param connectedChannelID
     *        the ID of the channel this channel is connected to
     */
    public InputChannel(final InputGate<T> inputGate, final int channelIndex, final ChannelID channelID,
            final ChannelID connectedChannelID, ChannelType type) {
        super(channelIndex, channelID, connectedChannelID, type);
        this.inputGate = inputGate;
        this.deserializer = new AdaptiveSpanningRecordDeserializer<T>();
    }

    /**
     * Returns the input gate associated with the input channel.
     * 
     * @return the input gate associated with the input channel.
     */
    public InputGate<T> getInputGate() {
        return this.inputGate;
    }

    /**
     * Reads a record from the input channel. If currently no record is available the method
     * returns <code>null</code>. If the channel is closed (i.e. no more records will be received), the method
     * throws an {@link EOFException}.
     * 
     * @return a record that has been transported through the channel or <code>null</code> if currently no record is
     *         available
     * @throws IOException
     *         thrown if the input channel is already closed {@link EOFException} or a transmission error has occurred
     */
    //   public abstract InputChannelResult readRecord(T target) throws IOException;

    /**
     * Immediately closes the input channel. The corresponding output channels are
     * notified if necessary. Any remaining records in any buffers or queue is considered
     * irrelevant and is discarded.
     * 
     * @throws InterruptedException
     *         thrown if the thread is interrupted while waiting for the channel to close
     * @throws IOException
     *         thrown if an I/O error occurs while closing the channel
     */
    //   public abstract void close() throws IOException, InterruptedException;

    @Override
    public boolean isInputChannel() {
        return true;
    }

    @Override
    public JobID getJobID() {
        return this.inputGate.getJobID();
    }

    //   public abstract AbstractTaskEvent getCurrentEvent();

    private DeserializationResult lastDeserializationResult;

    public InputChannelResult readRecord(T target) throws IOException {
        if (this.dataBuffer == null) {
            if (isClosed()) {
                return InputChannelResult.END_OF_STREAM;
            }

            // get the next element we need to handle (buffer or event)
            BufferOrEvent boe = getNextBufferOrEvent();

            if (boe == null) {
                throw new IllegalStateException(
                        "Input channel was queries for data even though none was announced available.");
            }

            // handle events
            if (boe.isEvent()) {
                // sanity check: an event may only come after a complete record.
                if (this.deserializer.hasUnfinishedData()) {
                    throw new IllegalStateException(
                            "Channel received an event before completing the current partial record.");
                }

                AbstractEvent evt = boe.getEvent();
                if (evt.getClass() == ChannelCloseEvent.class) {
                    this.brokerAggreedToCloseChannel = true;
                    return InputChannelResult.END_OF_STREAM;
                } else if (evt.getClass() == EndOfSuperstepEvent.class) {
                    return InputChannelResult.END_OF_SUPERSTEP;
                } else if (evt instanceof AbstractTaskEvent) {
                    this.currentEvent = (AbstractTaskEvent) evt;
                    return InputChannelResult.TASK_EVENT;
                } else {
                    LOG.error("Received unknown event: " + evt);
                    return InputChannelResult.NONE;
                }
            } else {
                // buffer case
                this.dataBuffer = boe.getBuffer();
                this.deserializer.setNextMemorySegment(this.dataBuffer.getMemorySegment(), this.dataBuffer.size());
            }
        }

        DeserializationResult deserializationResult = this.deserializer.getNextRecord(target);
        this.lastDeserializationResult = deserializationResult;

        if (deserializationResult.isBufferConsumed()) {
            releasedConsumedReadBuffer(this.dataBuffer);
            this.dataBuffer = null;
        }

        if (deserializationResult == DeserializationResult.INTERMEDIATE_RECORD_FROM_BUFFER) {
            return InputChannelResult.INTERMEDIATE_RECORD_FROM_BUFFER;
        } else if (deserializationResult == DeserializationResult.LAST_RECORD_FROM_BUFFER) {
            return InputChannelResult.LAST_RECORD_FROM_BUFFER;
        } else if (deserializationResult == DeserializationResult.PARTIAL_RECORD) {
            return InputChannelResult.NONE;
        } else {
            throw new IllegalStateException();
        }
    }

    @Override
    public ChannelType getChannelType() {
        return null;
    }

    @Override
    public boolean isClosed() throws IOException {
        if (this.ioException != null) {
            throw new IOException("An error occurred in the channel: " + this.ioException.getMessage(),
                    this.ioException);
        } else {
            return this.brokerAggreedToCloseChannel;
        }
    }

    public void close() throws IOException, InterruptedException {

        this.deserializer.clear();
        if (this.dataBuffer != null) {
            releasedConsumedReadBuffer(this.dataBuffer);
            this.dataBuffer = null;
        }

        // This code fragment makes sure the isClosed method works in case the channel input has not been fully consumed
        while (!this.brokerAggreedToCloseChannel) {
            BufferOrEvent next = getNextBufferOrEvent();
            if (next != null) {
                if (next.isEvent()) {
                    if (next.getEvent() instanceof ChannelCloseEvent) {
                        this.brokerAggreedToCloseChannel = true;
                    }
                } else {
                    releasedConsumedReadBuffer(next.getBuffer());
                }
            } else {
                Thread.sleep(200);
            }
        }

        // Send close event to indicate the input channel has successfully
        // processed all data it is interested in.
        transferEventToOutputChannel(new ChannelCloseEvent());
    }

    private void releasedConsumedReadBuffer(Buffer buffer) {
        this.amountOfDataTransmitted += buffer.size();
        buffer.recycleBuffer();
    }

    public void notifyGateThatInputIsAvailable() {
        this.getInputGate().notifyRecordIsAvailable(getIndex());
    }

    @Override
    public void transferEvent(AbstractEvent event) throws IOException, InterruptedException {
        transferEventToOutputChannel(event);
    }

    public void reportIOException(IOException ioe) {
        this.ioException = ioe;
    }

    @Override
    public void releaseAllResources() {
        this.brokerAggreedToCloseChannel = true;
        this.deserializer.clear();

        // The buffers are recycled by the input channel wrapper
    }

    /**
     * Notify the channel that a data unit has been consumed.
     */
    public void notifyDataUnitConsumed() {
        this.getInputGate().notifyDataUnitConsumed(getIndex());
    }

    public AbstractTaskEvent getCurrentEvent() {
        AbstractTaskEvent e = this.currentEvent;
        this.currentEvent = null;
        return e;
    }

    // InputChannelContext

    @Override
    public void queueEnvelope(Envelope envelope) {
        // The sequence number of the envelope to be queued
        final int sequenceNumber = envelope.getSequenceNumber();

        synchronized (this.queuedEnvelopes) {

            if (this.destroyCalled) {
                final Buffer buffer = envelope.getBuffer();
                if (buffer != null) {
                    buffer.recycleBuffer();
                }
                return;
            }

            final int expectedSequenceNumber = this.lastReceivedEnvelope + 1;
            if (sequenceNumber != expectedSequenceNumber) {
                // This is a problem, now we are actually missing some data
                reportIOException(new IOException(
                        "Expected data packet " + expectedSequenceNumber + " but received " + sequenceNumber));

                // notify that something (an exception) is available
                notifyGateThatInputIsAvailable();

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Input channel " + this.toString() + " expected envelope " + expectedSequenceNumber
                            + " but received " + sequenceNumber);
                }

                // rescue the buffer
                final Buffer buffer = envelope.getBuffer();
                if (buffer != null) {
                    buffer.recycleBuffer();
                }
            } else {

                this.queuedEnvelopes.add(envelope);
                this.lastReceivedEnvelope = sequenceNumber;
                this.lastSourceID = envelope.getSource();

                // Notify the channel about the new data. notify as much as there is (buffer plus once per event)
                if (envelope.getBuffer() != null) {
                    notifyGateThatInputIsAvailable();
                }

                List<? extends AbstractEvent> events = envelope.deserializeEvents();

                if (events != null) {
                    for (int i = 0; i < events.size(); i++) {
                        notifyGateThatInputIsAvailable();
                    }
                }
            }
        }
    }

    @Override
    public void destroy() {
        final Queue<Buffer> buffersToRecycle = new ArrayDeque<Buffer>();

        synchronized (this.queuedEnvelopes) {
            this.destroyCalled = true;

            while (!this.queuedEnvelopes.isEmpty()) {
                final Envelope envelope = this.queuedEnvelopes.poll();
                if (envelope.getBuffer() != null) {
                    buffersToRecycle.add(envelope.getBuffer());
                }
            }
        }

        while (!buffersToRecycle.isEmpty()) {
            buffersToRecycle.poll().recycleBuffer();
        }
    }

    public void logQueuedEnvelopes() {
        int numberOfQueuedEnvelopes = 0;
        int numberOfQueuedEnvelopesWithMemoryBuffers = 0;
        int numberOfQueuedEnvelopesWithFileBuffers = 0;

        synchronized (this.queuedEnvelopes) {

            final Iterator<Envelope> it = this.queuedEnvelopes.iterator();
            while (it.hasNext()) {

                final Envelope envelope = it.next();
                ++numberOfQueuedEnvelopes;
                final Buffer buffer = envelope.getBuffer();
                if (buffer == null) {
                    continue;
                }

                ++numberOfQueuedEnvelopesWithMemoryBuffers;
            }
        }

        System.out.println("\t\t" + this.toString() + ": " + numberOfQueuedEnvelopes + " ("
                + numberOfQueuedEnvelopesWithMemoryBuffers + ", " + numberOfQueuedEnvelopesWithFileBuffers + ")");

    }

    @Override
    public Buffer requestBuffer(int minBufferSize) throws IOException {
        return this.inputGate.requestBuffer(minBufferSize);
    }

    @Override
    public Buffer requestBufferBlocking(int minBufferSize) throws IOException, InterruptedException {
        return this.inputGate.requestBufferBlocking(minBufferSize);
    }

    @Override
    public int getBufferSize() {
        return this.inputGate.getBufferSize();
    }

    @Override
    public void reportAsynchronousEvent() {
        this.inputGate.reportAsynchronousEvent();
    }

    @Override
    public BufferAvailabilityRegistration registerBufferAvailabilityListener(BufferAvailabilityListener listener) {
        return this.inputGate.registerBufferAvailabilityListener(listener);
    }

    // ChannelBroker

    public BufferOrEvent getNextBufferOrEvent() throws IOException {
        // return pending events first
        if (this.pendingEvents != null) {
            // if the field is not null, it must always have a next value!
            BufferOrEvent next = new BufferOrEvent(this.pendingEvents.next());
            if (!this.pendingEvents.hasNext()) {
                this.pendingEvents = null;
            }
            return next;
        }

        // if no events are pending, get the next buffer
        Envelope nextEnvelope;
        synchronized (this.queuedEnvelopes) {
            if (this.queuedEnvelopes.isEmpty()) {
                return null;
            }
            nextEnvelope = this.queuedEnvelopes.poll();
        }

        // schedule events as pending, because events come always after the buffer!
        List<AbstractEvent> events = (List<AbstractEvent>) nextEnvelope.deserializeEvents();
        Iterator<AbstractEvent> eventsIt = events.iterator();
        if (eventsIt.hasNext()) {
            this.pendingEvents = eventsIt;
        }

        // get the buffer, if there is one
        if (nextEnvelope.getBuffer() != null) {
            return new BufferOrEvent(nextEnvelope.getBuffer());
        } else if (this.pendingEvents != null) {
            // if the field is not null, it must always have a next value!
            BufferOrEvent next = new BufferOrEvent(this.pendingEvents.next());
            if (!this.pendingEvents.hasNext()) {
                this.pendingEvents = null;
            }

            return next;
        } else {
            // no buffer and no events, this should be an error
            throw new IOException("Received an envelope with neither data nor events.");
        }
    }

    public void transferEventToOutputChannel(AbstractEvent event) throws IOException, InterruptedException {
        Envelope ephemeralEnvelope = new Envelope(0, getJobID(), getID());
        ephemeralEnvelope.serializeEventList(Arrays.asList(event));

        this.envelopeDispatcher.dispatchFromInputChannel(ephemeralEnvelope);
    }
}