class GrinderThread implements Runnable {

  @Override
  public void run() {
    m_processLifeCycle.threadStarted(m_context);

    m_context.setCurrentRunNumber(-1);

    // Fire begin thread event before creating the worker runnable to allow
    // plug-ins to do per-thread initialisation required by the script code.
    m_context.fireBeginThreadEvent();

    try {
      final WorkerRunnable workerRunnable = m_workerRunnableFactory.create();

      final int numberOfRuns = m_properties.getInt("grinder.runs", 1);

      if (numberOfRuns == 0) {
        m_logger.info(m_context.getLogMarker(), "starting, will run forever");
      }
      else {
        m_logger.info(m_context.getLogMarker(),
                      "starting, will do {} run{}",
                      numberOfRuns,
                      numberOfRuns == 1 ? "" : "s");
      }

      m_threadSynchronisation.awaitStart();

      m_sleeper.sleepFlat(m_properties.getLong("grinder.initialSleepTime", 0));

      int currentRun;

      for (currentRun = 0;
           numberOfRuns == 0 || currentRun < numberOfRuns;
           currentRun++) {

        m_context.setCurrentRunNumber(currentRun);

        m_context.fireBeginRunEvent();

        try {
          workerRunnable.run();
        }
        catch (final ScriptExecutionException e) {
          final Throwable cause = e.getCause();

          if (cause instanceof ShutdownException ||
              cause instanceof Sleeper.ShutdownException) {
            m_logger.info(m_context.getLogMarker(), "shut down");
            break;
          }

          m_logger.error(m_context.getLogMarker(),
                         "aborted run - " + e.getShortMessage(),
                         e);
        }

        m_context.fireEndRunEvent();
      }

      m_context.setCurrentRunNumber(-1);

      m_logger.info(m_context.getLogMarker(),
                    "finished {} {}",
                    currentRun,
                    currentRun == 1 ? "run" : "runs");

      m_context.fireBeginShutdownEvent();

      try {
        workerRunnable.shutdown();
      }
      catch (final ScriptExecutionException e) {
        m_logger.error(m_context.getLogMarker(),
                       "aborted test runner shut down - " + e.getShortMessage(),
                       e);
      }

      m_context.fireEndThreadEvent();
    }
    catch (final ScriptExecutionException e) {
      m_logger.error(m_context.getLogMarker(),
                     "aborting thread - {}" + e.getShortMessage(),
                     e);
    }
    catch (final Exception e) {
      m_logger.error(m_context.getLogMarker(), "aborting thread - " + e, e);
    }
    finally {
      m_context.setCurrentRunNumber(-1);

      m_threadSynchronisation.threadFinished();
    }
  }

  public  GrinderThread(final Logger logger,
                       final ThreadContext context,
                       final WorkerThreadSynchronisation threadSynchronisation,
                       final ProcessLifeCycleListener processLifeCycle,
                       final GrinderProperties properties,
                       final Sleeper sleeper,
                       final WorkerRunnableFactory workerRunnableFactory);

}