Package winterwell.utils.threads

Source Code of winterwell.utils.threads.ATask

package winterwell.utils.threads;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;

import winterwell.utils.StrUtils;
import winterwell.utils.TimeOut;
import winterwell.utils.Utils;
import winterwell.utils.io.FileUtils;
import winterwell.utils.io.SysOutCollectorStream;
import winterwell.utils.time.Dt;
import winterwell.utils.time.Time;

/**
*
* Provides {@link #cancel()}. When run, sets the running thread's name for
* debugging info.
*
* @author daniel
*
* @param <V>
*            return type
*/
public abstract class ATask<V> implements Callable<V>, IProgress {

  /**
   * The enum ordering follows the lifecycle, so you can use &lt; to compare
   * states. E.g.
   * <code>while(status.ordinal() < QStatus.STOPPING.ordinal()) {do stuff}</code>
   */
  public static enum QStatus {
    /** Initial putting-the-task-together state. */ NOT_SUBMITTED,
    /** Start requested. */ WAITING,
    /** Doing It's Thang! */ RUNNING,
    /** Stop requested but not yet processed */ STOPPING,
    /** Stopped: properly*/ DONE,
    /** Stopped with error */ ERROR,     
    /** Stopped: user cancelled */ CANCELLED;

    /**
     * @return true for the finished states of DONE, ERROR and CANCELLED.<br>
     *         false for everything earlier (including STOPPING).
     */
    public boolean isFinished() {
      return ordinal() >= DONE.ordinal();
    }
  }

  boolean captureStdOut;

  /**
   * Time the task stopped running. null if not run yet.
   */
  private Time end;

  /**
   * If >0, we will set a TimeOut
   */
  private long maxTime;

  private final String name;

  private V output;

  transient TaskRunner runner;

  /**
   * Time the task started running. null if not started yet.
   */
  private Time start;

  private volatile QStatus status = QStatus.NOT_SUBMITTED;

  private transient SysOutCollectorStream sysOut;

  private transient Thread thread;

  public ATask() {
    this(null);
  }

  /**
   *
   * @param name
   *            This will be used as the thread name whilst running.
   */
  public ATask(String name) {
    this.name = name;
  }

  /**
   * Wraps {@link #run()} with timing, thread-naming, and capturing std-out
   * (if set to do so)
   */
  @Override
  public final V call() throws Exception {
    TimeOut timeOut = null;
    try {
      if (status == QStatus.CANCELLED)
        throw new CancellationException();
      start = new Time();
      // set a timer running
      // - can lead to InterruptedExceptions
      if (maxTime > 0) {
        timeOut = new TimeOut(maxTime);
      }
      // assert runner != null;
      status = QStatus.RUNNING;
      thread = Thread.currentThread();
      // Set the thread name (but keep it short)
      // This will always be reset to Done: or Error: by the end of the
      // method call
      thread.setName(StrUtils.ellipsize(name == null ? toString() : name,
          32));
      if (captureStdOut) {
        sysOut = new SysOutCollectorStream();
      }

      // run!
      output = run();

      end = new Time();
      status = QStatus.DONE;
      thread.setName(StrUtils.ellipsize("Done: " + thread.getName(), 32));
      return output;
    } catch (Throwable e) {
      status = QStatus.ERROR;
      if (runner != null) {
        runner.report(this, e);
      }
      thread.setName(StrUtils.ellipsize("Error: " + thread.getName(), 32));
      // There's not much point throwing an exception from within an
      // executor
      // - but it's useful in debugging.
      throw Utils.runtime(e);
    } finally {
      if (timeOut != null) {
        timeOut.cancel();
      }
      if (runner != null) {
        runner.done(this);
      }
      // drop references
      runner = null;
      thread = null;
      FileUtils.close(sysOut);
    }
  }

  /**
   * Cancel this task - it will be skipped over instead of running. Should
   * only be called on tasks which have status == WAITING (or status ==
   * CANCELLED in which case it does nothing).
   * <p>
   * The effects of cancelling a running task are undefined. They depend on
   * {@link #cancel2_running()}
   *
   * @throws IllegalStateException
   */
  public void cancel() throws IllegalStateException {
    switch (status) {
    case CANCELLED:
      return; // no-op
    case WAITING:
      break;
    case NOT_SUBMITTED:
      break;
    case DONE:
      return; // no-op
    case RUNNING:
      // what to do?
      cancel2_running();
    }
    if (runner != null) {
      runner.done(this);
    }
    status = QStatus.CANCELLED;
  }

  protected void cancel2_running() throws IllegalStateException {
    // the finally clause should take care of setting thread=null
    int cnt = 0;
    while (thread != null) {
      thread.interrupt();
      cnt++;
      if (cnt == 10) {
        // No joy? then be more aggressive
        thread.stop();
        break;
      }
      Utils.sleep(100);
    }
  }

  /**
   * Implement a task-specific version of equals() and hashCode() if you want
   * to avoid duplicate tasks.
   */
  @Override
  public boolean equals(Object obj) {
    return super.equals(obj);
  }

  /**
   * Get intermediate results. Override to do anything - the default returns
   * null.
   *
   * @return
   * @see #getProgress()
   */
  public V getIntermediateOutput() {
    return null;
  }

  public String getName() {
    return name;
  }

  /**
   * Only valid if status == DONE
   *
   * @return
   */
  public V getOutput() {
    assert status == QStatus.DONE : status;
    return output;
  }

  @Override
  public double[] getProgress() {
    return null;
  }

  public Dt getRunningTime() {
    if (start == null)
      return Dt.ZERO();
    return start.dt(end == null ? new Time() : end);
  }

  public QStatus getStatus() {
    return status;
  }

  /**
   * Output sent to standard out via System.out during this Task's run. Only
   * valid if {@link #setCaptureStdOut(boolean)} was set to true. Otherwise
   * will return null.
   *
   * @see #isCapturingStdOut()
   */
  public String getStdOut() {
    // assert status == QStatus.DONE || status == QStatus.RUNNING : status;
    if (sysOut == null)
      return null;
    return sysOut.toString();
  }

  /**
   * Implement a task-specific version of equals() and hashCode() if you want
   * to avoid duplicate tasks.
   */
  @Override
  public int hashCode() {
    return super.hashCode();
  }

  /**
   * TODO interrupt the running thread
   *
   * @throws IllegalStateException
   */
  public void interrupt() throws IllegalStateException {
    if (status == QStatus.CANCELLED)
      return; // no-op
    if (status != QStatus.RUNNING)
      throw new IllegalStateException(status + " " + this);
    // Test: what does this do? inc to the queue
    thread.interrupt();
  }

  public boolean isCapturingStdOut() {
    return captureStdOut;
  }

  /**
   * Do whatever it is this task does!
   *
   * @return
   * @throws Exception
   */
  protected abstract V run() throws Exception;

  public void setCaptureStdOut(boolean captureStdOut) {
    this.captureStdOut = captureStdOut;
    // Were we capturing? Should we stop?
    if (!captureStdOut && sysOut != null) {
      FileUtils.close(sysOut);
    }
    // Already running? Should we start capturing std-out from now?
    if (captureStdOut && getStatus() == QStatus.RUNNING && sysOut == null) {
      sysOut = new SysOutCollectorStream();
    }
  }

  /**
   * @param null for no timeout. This will use a {@link TimeOut} to interrupt
   *        the thread.
   */
  public void setMaxTime(Dt maxTime) {
    this.maxTime = maxTime == null ? 0 : maxTime.getMillisecs();
  }

  void setTaskRunner(TaskRunner runner) {
    assert this.runner == null : "Task " + this
        + " already assigned to runner " + this.runner;
    this.runner = runner;
    status = QStatus.WAITING;
  }

  @Override
  public String toString() {
    if (name != null)
      return getClass().getName() + "[" + name + "]";
    // have a predictable name for TaskRunnerWithStats
    return getClass().getName();
  }

}
TOP

Related Classes of winterwell.utils.threads.ATask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.