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

org.apache.hadoop.mapred.SimulatedTaskRunner Maven / Gradle / Ivy

package org.apache.hadoop.mapred;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.concurrent.PriorityBlockingQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;


/**
 * This class accepts tasks to 'run' and finish after a certain time.
 * No actual work is done, the task is only marked as completed after a delay.
 * The advantage of using this class is that no JVM is launched,
 * saving CPU/memory. Used only for when the task tracker is used to simulate
 * load for the job tracker.
 */
public class SimulatedTaskRunner extends Thread{
  public static final Log LOG =
      LogFactory.getLog(SimulatedTaskRunner.class);

  /**
   * Helper class for associating a TIP and the time that the TIP is supposed
   * to finish. Implements comparable so that this can be inserted into a
   * priority queue.
   */
  class TipToFinish implements Comparable {

    final TaskInProgress tip;
    final long timeToFinish;
    final TaskUmbilicalProtocol umbilicalProtocol;

    public TipToFinish(
      TaskInProgress tip,
      long timeToFinish,
      TaskUmbilicalProtocol umbilicalProtocol) {
      this.tip = tip;
      this.timeToFinish = timeToFinish;
      this.umbilicalProtocol = umbilicalProtocol;
    }

    public long getTimeToFinish() {
      return timeToFinish;
    }

    public TaskInProgress getTip() {
      return tip;
    }

    @Override
    public int compareTo(TipToFinish o) {
      long otherTimeToFinish = o.timeToFinish;
      if (this.timeToFinish < otherTimeToFinish) {
        return -1;
      } else if (this.timeToFinish > otherTimeToFinish) {
        return 1;
      } else {
        // Must be equal
        return 0;
      }
    }
    @Override
    public String toString() {
      return "<" + tip.getTask().getTaskID().toString() + "," + timeToFinish +
          ">";
    }
    @Override
    public boolean equals(Object o) {
      if (!(o instanceof TipToFinish)) {
        return false;
      }
      TipToFinish ttf = (TipToFinish)o;
      return this.timeToFinish == ttf.timeToFinish;
    }

    /**
     * Finish the tip.
     */
    public void finishTip() {
      LOG.info("Finishing TIP " + tip.getTask().getTaskID() +
        " with status " +
        tip.getStatus() + " and isTaskCleanupTask is : " +
        tip.getTask().isTaskCleanupTask() + " and phase is " +
        tip.getTask().getPhase() + " and finish time " +
        timeToFinish + " at " +
        System.currentTimeMillis());
      try {
        umbilicalProtocol.done(tip.getTask().getTaskID());
        taskTracker.cleanupUmbilical(umbilicalProtocol);
      } catch (IOException e) {
        // This shouldn't happen as done does not really throw an IOE
        LOG.fatal("Error while trying to call done on " +
          tip.getTask().getTaskID(), e);
        System.exit(-1);
      }
      tip.reportTaskFinished(false);
      LOG.debug("After finishing, " + tip.getTask().getTaskID() + " has status " +
        tip.getStatus() + " and isTaskCleanupTask is : " +
        tip.getTask().isTaskCleanupTask() + " and phase is " +
        tip.getTask().getPhase());
    }
  }

  // Upper bound on time that a task should wait before finishing
  private long timeToFinishTask = 0;
  // Random number generator for task finish times
  private Random rand = new Random();
  // Queue of tips to finish. Ordered by finished time. Must be threadsafe.
  private PriorityBlockingQueue tipQueue =
      new PriorityBlockingQueue();
  // Reference to the tracker for calling done and getting map task completion
  // events
  private TaskTracker taskTracker;
  // A mapping from the (reduce) TIP's to the mapper waiting thread. Used to
  // interrupt the mapper waiting thread in case the task gets killed
  private Map mapperWaitThreadMap =
      Collections.synchronizedMap(
          new HashMap());
  /**
   * @param timeToFinishTask task will finish randomly between 0 and this many
   * miliseconds with a uniform distribution
   * @param t the reference to the TaskTracker for calling completion methods
   */
  public SimulatedTaskRunner(long timeToFinishTask, TaskTracker t) {
    this.taskTracker = t;
    this.timeToFinishTask = timeToFinishTask;
    // If it's not a daemon thread, this might prevent the TT from exiting.
    this.setDaemon(true);
    // Name the thread something like "SimulatedTaskRunner Thread-xyz.."
    this.setName("SimulatedTaskRunner " + this.getName());
  }

  /**
   * The primary public method that should be called to 'run' a task. Handles
   * both map and reduce tasks and marks them as completed after the configured
   * time interval
   * @param tip
   */
  public void launchTask(TaskInProgress tip) throws IOException {
    LOG.info("Launching simulated task " + tip.getTask().getTaskID() +
        " for job " + tip.getTask().getJobID());
    TaskUmbilicalProtocol umbilicalProtocol = taskTracker.getUmbilical(tip);
    // For map tasks, we can just finish the task after some time. Same thing
    // with cleanup tasks, as we don't need to be waiting for mappers to finish
    if (tip.getTask().isMapTask() || tip.getTask().isTaskCleanupTask() ||
      tip.getTask().isJobCleanupTask() || tip.getTask().isJobSetupTask() ) {
      addTipToFinish(tip, umbilicalProtocol);
    } else {
      MapperWaitThread mwt =
          new MapperWaitThread(tip, this, umbilicalProtocol);
      // Save a reference to the mapper wait thread so that we can stop them if
      // the task gets killed
      mapperWaitThreadMap.put(tip, mwt);
      mwt.start();
    }

  }

  /**
   * Add the specified TaskInProgress to the priority queue of tasks to finish.
   * @param tip
   * @param umbilicalProtocol
   */
  protected void addTipToFinish(TaskInProgress tip,
                                TaskUmbilicalProtocol umbilicalProtocol) {
    long currentTime = System.currentTimeMillis();
    long finishTime = currentTime + Math.abs(rand.nextLong()) %
        timeToFinishTask;
    LOG.info("Adding TIP " + tip.getTask().getTaskID() +
        " to finishing queue with start time " +
        currentTime + " and finish time " + finishTime +
        " (" + ((finishTime - currentTime) / 1000.0) + " sec) to thread " +
        getName());
    TipToFinish ttf = new TipToFinish(tip, finishTime, umbilicalProtocol);
    tipQueue.put(ttf);
    // Interrupt the waiting thread. We could put in additional logic to only
    // interrupt when necessary, but probably not worth the complexity.
    this.interrupt();
  }


  /**
   * Continuously looks through the queue of TIP's to mark as finished,
   * finishing and sleeping as necessary. Can be interrupted while it's sleeping
   * if it needs to re-evaluate how long to sleep.
   */
  @Override
  public void run() {
    while (true) {
      // Wait to get a TIP
      TipToFinish ttf = null;
      try {
        LOG.debug("Waiting for a TIP");
        ttf = tipQueue.take();
      } catch (InterruptedException e) {
        LOG.info("Got interrupted exception while waiting to take()");
        continue;
      }
      LOG.debug(" Got a TIP " + ttf.getTip().getTask().getTaskID() +
          " at time " + System.currentTimeMillis() + " with finish time " +
          ttf.getTimeToFinish());
      // Wait until it's time to finish the task. Since the TIP was pulled from
      // the priority queue, this should be the first task in the queue that
      // needs to be finished. If we get interrupted, that means that it's
      // possible that we added a TIP that should finish earlier
      boolean interrupted = false;
      while (true) {
        long currentTime = System.currentTimeMillis();
        if (currentTime < ttf.getTimeToFinish()) {
          try {
            long sleepTime = ttf.getTimeToFinish() - currentTime;
            LOG.debug("Sleeping for " + sleepTime + " ms");
            Thread.sleep(sleepTime);
          } catch (InterruptedException e) {
            LOG.debug("Finisher thread was interrupted", e);
            interrupted = true;
            break;
          }
        } else {
          break;
        }
      }

      // Wait was interrupted, then it could mean that we added a task
      // that needs to finish sooner. Put that task back and start again
      if (interrupted) {
        LOG.info("Putting back TIP " + ttf.getTip().getTask().getTaskID() +
            " for job " + ttf.getTip().getTask().getJobID());
        tipQueue.put(ttf);
        continue;
      }

      // Finish the task
      TaskInProgress tip = ttf.getTip();
      ttf.finishTip();

      // Also clean up the mapper wait thread map for reducers. It should exist
      // for reduce tasks that are not cleanup tasks
      if (!tip.getTask().isMapTask() &&
          !tip.getTask().isTaskCleanupTask() &&
          !tip.getTask().isJobCleanupTask() &&
          !tip.getTask().isJobSetupTask()) {
        if (!mapperWaitThreadMap.containsKey(tip)) {
          throw new RuntimeException("Unable to find mapper wait thread for " +
              tip.getTask().getTaskID() + " job " + tip.getTask().getJobID());
        }
        LOG.debug("Removing mapper wait thread for " +
            tip.getTask().getTaskID() + " job " + tip.getTask().getJobID());
        mapperWaitThreadMap.remove(tip);
      } else if (mapperWaitThreadMap.containsKey(tip)) {
        throw new RuntimeException("Mapper wait thread exists for" +
            tip.getTask().getTaskID() + " job " + tip.getTask().getJobID() +
            " when it shouldn't!");
      }
    }
  }

  /**
   * @param tip the TaskInProgress to remove from the queue of TIP's that need
   * to be finished.
   */
  private void removeFromFinishingQueue(TaskInProgress tip) {
    LOG.debug("Removing " + tip.getTask().getTaskID() +
        " from finishig queue");
    tipQueue.remove(tip);
  }

  /**
   * Called in case the task needs to be killed. Canceling will kill any map
   * wait threads and also remove it from the queue of tasks that should be
   * marked as finished.
   * @param tip the killed TaskInProgress
   */
  public void cancel(TaskInProgress tip) {
    LOG.info("Canceling task "  + tip.getTask().getTaskID() + " of job " +
        tip.getTask().getJobID());
    // Cancel & remove the map completion finish thread for reduce tasks.
    if (!tip.getTask().isMapTask() && !tip.getTask().isTaskCleanupTask()) {
      if (!mapperWaitThreadMap.containsKey(tip)) {
        throw new RuntimeException("Mapper wait thread doesn't exist " +
            "for " + tip.getTask().getTaskID());
      }
      LOG.debug("Interrupting mapper wait thread for " +
          tip.getTask().getTaskID() + " job " +
          tip.getTask().getJobID());
      mapperWaitThreadMap.get(tip).interrupt();
      LOG.debug("Removing mapper wait thread for " +
          tip.getTask().getTaskID() + " job " + tip.getTask().getJobID());
      mapperWaitThreadMap.remove(tip);
    } else {
      LOG.debug(tip.getTask().getTaskID() + " is not a reduce task, so " +
          "not canceling mapper wait thread");
    }
    removeFromFinishingQueue(tip);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy