public void releaseAllResources() throws IOException {
    synchronized (requestLock) {
      if (!isReleased) {
        try {
          LOG.debug("{}: Releasing {}.", owningTaskName, this);

          if (retriggerLocalRequestTimer != null) {
            retriggerLocalRequestTimer.cancel();
          }

          for (InputChannel inputChannel : inputChannels.values()) {
            try {
              inputChannel.releaseAllResources();
            } catch (IOException e) {
              LOG.warn("Error during release of channel resources: " + e.getMessage(), e);
            }
          }

          // The buffer pool can actually be destroyed immediately after the
          // reader received all of the data from the input channels.
          if (bufferPool != null) {
            bufferPool.lazyDestroy();
          }
        } finally {
          isReleased = true;
        }
      }
    }
  }
  /** Retriggers a partition request. */
  public void retriggerPartitionRequest(IntermediateResultPartitionID partitionId)
      throws IOException, InterruptedException {
    synchronized (requestLock) {
      if (!isReleased) {
        final InputChannel ch = inputChannels.get(partitionId);

        checkNotNull(ch, "Unknown input channel with ID " + partitionId);

        LOG.debug(
            "Retriggering partition request {}:{}.", ch.partitionId, consumedSubpartitionIndex);

        if (ch.getClass() == RemoteInputChannel.class) {
          final RemoteInputChannel rch = (RemoteInputChannel) ch;
          rch.retriggerSubpartitionRequest(consumedSubpartitionIndex);
        } else if (ch.getClass() == LocalInputChannel.class) {
          final LocalInputChannel ich = (LocalInputChannel) ch;

          if (retriggerLocalRequestTimer == null) {
            retriggerLocalRequestTimer = new Timer(true);
          }

          ich.retriggerSubpartitionRequest(retriggerLocalRequestTimer, consumedSubpartitionIndex);
        } else {
          throw new IllegalStateException(
              "Unexpected type of channel to retrigger partition: " + ch.getClass());
        }
      }
    }
  }
  @Test
  public void testExponentialNoBackoff() throws Exception {
    InputChannel ch = createInputChannel(0, 0);

    assertEquals(0, ch.getCurrentBackoff());

    assertFalse(ch.increaseBackoff());
    assertEquals(0, ch.getCurrentBackoff());
  }
  @Override
  public void sendTaskEvent(TaskEvent event) throws IOException {
    synchronized (requestLock) {
      for (InputChannel inputChannel : inputChannels.values()) {
        inputChannel.sendTaskEvent(event);
      }

      if (numberOfUninitializedChannels > 0) {
        pendingEvents.add(event);
      }
    }
  }
  @Override
  public boolean isFinished() {
    synchronized (requestLock) {
      for (InputChannel inputChannel : inputChannels.values()) {
        if (!inputChannel.isReleased()) {
          return false;
        }
      }
    }

    return true;
  }
  public void updateInputChannel(InputChannelDeploymentDescriptor icdd)
      throws IOException, InterruptedException {
    synchronized (requestLock) {
      if (isReleased) {
        // There was a race with a task failure/cancel
        return;
      }

      final IntermediateResultPartitionID partitionId =
          icdd.getConsumedPartitionId().getPartitionId();

      InputChannel current = inputChannels.get(partitionId);

      if (current.getClass() == UnknownInputChannel.class) {

        UnknownInputChannel unknownChannel = (UnknownInputChannel) current;

        InputChannel newChannel;

        ResultPartitionLocation partitionLocation = icdd.getConsumedPartitionLocation();

        if (partitionLocation.isLocal()) {
          newChannel = unknownChannel.toLocalInputChannel();
        } else if (partitionLocation.isRemote()) {
          newChannel = unknownChannel.toRemoteInputChannel(partitionLocation.getConnectionId());
        } else {
          throw new IllegalStateException("Tried to update unknown channel with unknown channel.");
        }

        LOG.debug("Updated unknown input channel to {}.", newChannel);

        inputChannels.put(partitionId, newChannel);

        newChannel.requestSubpartition(consumedSubpartitionIndex);

        for (TaskEvent event : pendingEvents) {
          newChannel.sendTaskEvent(event);
        }

        if (--numberOfUninitializedChannels == 0) {
          pendingEvents.clear();
        }
      }
    }
  }
  public void setInputChannel(
      IntermediateResultPartitionID partitionId, InputChannel inputChannel) {
    synchronized (requestLock) {
      if (inputChannels.put(checkNotNull(partitionId), checkNotNull(inputChannel)) == null
          && inputChannel.getClass() == UnknownInputChannel.class) {

        numberOfUninitializedChannels++;
      }
    }
  }
  @Override
  public void requestPartitions() throws IOException, InterruptedException {
    // Sanity check
    if (numberOfInputChannels != inputChannels.size()) {
      throw new IllegalStateException(
          "Bug in input gate setup logic: mismatch between"
              + "number of total input channels and the currently set number of input "
              + "channels.");
    }

    if (!requestedPartitionsFlag) {
      synchronized (requestLock) {
        for (InputChannel inputChannel : inputChannels.values()) {
          inputChannel.requestSubpartition(consumedSubpartitionIndex);
        }
      }

      requestedPartitionsFlag = true;
    }
  }
  @Override
  public BufferOrEvent getNextBufferOrEvent() throws IOException, InterruptedException {

    if (hasReceivedAllEndOfPartitionEvents) {
      return null;
    }

    if (isReleased) {
      throw new IllegalStateException("Already released.");
    }

    requestPartitions();

    InputChannel currentChannel = null;
    while (currentChannel == null) {
      currentChannel = inputChannelsWithData.poll(2, TimeUnit.SECONDS);
    }

    final Buffer buffer = currentChannel.getNextBuffer();

    // Sanity check that notifications only happen when data is available
    if (buffer == null) {
      throw new IllegalStateException(
          "Bug in input gate/channel logic: input gate got"
              + "notified by channel about available data, but none was available.");
    }

    if (buffer.isBuffer()) {
      return new BufferOrEvent(buffer, currentChannel.getChannelIndex());
    } else {
      final AbstractEvent event = EventSerializer.fromBuffer(buffer, getClass().getClassLoader());

      if (event.getClass() == EndOfPartitionEvent.class) {
        channelsWithEndOfPartitionEvents.set(currentChannel.getChannelIndex());

        if (channelsWithEndOfPartitionEvents.cardinality() == numberOfInputChannels) {
          hasReceivedAllEndOfPartitionEvents = true;
        }

        currentChannel.notifySubpartitionConsumed();

        currentChannel.releaseAllResources();
      }

      return new BufferOrEvent(event, currentChannel.getChannelIndex());
    }
  }
  @Test
  public void testExponentialBackoffCappedAtMax() throws Exception {
    InputChannel ch = createInputChannel(500, 3000);

    assertEquals(0, ch.getCurrentBackoff());

    assertTrue(ch.increaseBackoff());
    assertEquals(500, ch.getCurrentBackoff());

    assertTrue(ch.increaseBackoff());
    assertEquals(1000, ch.getCurrentBackoff());

    assertTrue(ch.increaseBackoff());
    assertEquals(2000, ch.getCurrentBackoff());

    assertTrue(ch.increaseBackoff());
    assertEquals(3000, ch.getCurrentBackoff());

    assertFalse(ch.increaseBackoff());
    assertEquals(3000, ch.getCurrentBackoff());
  }