/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.harness;

/**
 * A timeout thread.  Exits the harness if it isn't cancelled in time.
 * <p>
 * Use like this:
 * <pre>
 *   TimeoutThread timeout = new TimeoutThread(10);
 *   .. do stuff ..
 *   timeout.cancel();
 * </pre>
 */
final class TimeoutThread implements Runnable {
  private static final boolean VERBOSE = false;
  private static final int MILLIS_PER_SECOND = 1000;
  private final long timeout;
  private boolean cancelled = false;
  private volatile boolean started = false;

  /**
   * Create a timeout object and start it running in its own
   * thread.
   * @param seconds Timeout in seconds
   */
  TimeoutThread(int seconds) {
    this.timeout = seconds * MILLIS_PER_SECOND;
    new Thread(this).start();
    synchronized (this) {
      while (!started) {
        try {
          /* Wait for the timeout thread to start running */
          wait();
        } catch (InterruptedException e) {
        }
      }
    }
  }

  @Override
  public void run() {
    long startTime = System.currentTimeMillis();
    synchronized (this) {
      /* Inform the caller that the timeout thread has started */
      started = true;
      notify();

      while (!cancelled) {
        try {
          /* Sleep until woken by a cancel or the timer has expired */
          long now = System.currentTimeMillis();
          if (now - startTime >= timeout) {
            System.err.printf("Collection exceeded timeout %dms%n",timeout);
            Main.exitWithFailure();
          }
          long sleepTime = Math.max(1,timeout - (now - startTime));
          if (VERBOSE) {
            System.err.printf("Collection timeout: sleeping for %dms%n",sleepTime);
          }
          wait(sleepTime);
        } catch (InterruptedException e) {
          // Ignore interruptions
        }
      }
    }
  }

  /** Cancel the timeout */
  public void cancel() {
    synchronized (this) {
      if (VERBOSE) {
        System.err.printf("Collection timeout: cancelled%n");
      }
      cancelled = true;
      notify();
    }
  }
}
