public void run() throws GrinderException {
		try {
			final GrinderProperties properties = m_initialisationMessage.getProperties();

			final ScriptEngineContainer scriptEngineContainer = new ScriptEngineContainer(properties, m_logger,
					DCRContextImplementation.create(m_logger), m_initialisationMessage.getScript());

			final WorkerIdentity workerIdentity = m_initialisationMessage.getWorkerIdentity();

			final StringBuilder numbers = new StringBuilder("worker process ");

			numbers.append(workerIdentity.getNumber());

			final int agentNumber = workerIdentity.getAgentIdentity().getNumber();

			if (agentNumber >= 0) {
				numbers.append(" of agent number ");
				numbers.append(agentNumber);
			}

			m_logger.info(numbers.toString());

			final short numberOfThreads = properties.getShort("grinder.threads", (short) 1);
			final int reportToConsoleInterval = properties.getInt("grinder.reportToConsole.interval", 500);
			final int duration = properties.getInt("grinder.duration", 0);

			final Instrumenter instrumenter = scriptEngineContainer.createInstrumenter();

			m_testRegistryImplementation.setInstrumenter(instrumenter);

			m_logger.info("Instrumentation agents: {}", instrumenter.getDescription());

			// Force initialisation of the script engine before we start the
			// message
			// pump. Jython 2.5+ tests to see whether the stdin stream is a tty,
			// and
			// on some versions of Windows, this synchronises on the stream
			// object's
			// monitor. This clashes with the message pump which starts a thread
			// to
			// call StreamRecevier.waitForMessage(), and so also synchronises on
			// that
			// monitor. See bug 2936167.

			final ScriptEngine scriptEngine = scriptEngineContainer
					.getScriptEngine(m_initialisationMessage.getScript());

			m_logger.info("Running \"{}\" using {}", m_initialisationMessage.getScript(),
					scriptEngine.getDescription());

			m_messagePump.start();

			// Don't write out the data log header until now as the script may
			// declare new statistics.

			final StringBuilder dataLogHeader = new StringBuilder("Thread, Run, Test, Start time (ms since Epoch)");

			final ExpressionView[] detailExpressionViews = m_statisticsServices.getDetailStatisticsView()
					.getExpressionViews();

			for (final ExpressionView detailExpressionView : detailExpressionViews) {
				dataLogHeader.append(", ");
				dataLogHeader.append(detailExpressionView.getDisplayName());
			}

			m_dataLogger.info(dataLogHeader.toString());

			sendStatusMessage(ProcessReport.STATE_STARTED, (short) 0, numberOfThreads);
			boolean threadRampUp = properties.getBoolean("grinder.threadRampUp", false);
			final ThreadSynchronisation threadSynchronisation = threadRampUp ?
					new ThreadRampUpEnabledThreadSynchronisation(m_eventSynchronisation, m_sleeper) :
					new ThreadSynchronisation(m_eventSynchronisation);

			m_terminalLogger.info("Starting threads");

			synchronized (m_eventSynchronisation) {
				m_threadStarter = new ThreadStarterImplementation(threadSynchronisation, scriptEngine);

				for (int i = 0; i < numberOfThreads; i++) {
					m_threadStarter.startThread(null);
				}
			}

			threadSynchronisation.startThreads();

			m_times.setExecutionStartTime();

			m_logger.info("Start time is {} ms since Epoch", m_times.getExecutionStartTime());

			final TimerTask reportTimerTask = new ReportToConsoleTimerTask(threadSynchronisation);
			final TimerTask shutdownTimerTask = new ShutdownTimerTask();

			// Schedule a regular statistics report to the console. We don't
			// need to schedule this at a fixed rate. Each report contains the
			// work done since the last report.

			// First (empty) report to console to start it recording if its
			// not already.
			reportTimerTask.run();

			final Timer timer = new Timer(true);

			timer.schedule(reportTimerTask, reportToConsoleInterval, reportToConsoleInterval);

			try {
				if (duration > 0) {
					m_terminalLogger.info("This test will shut down after {} ms", duration);

					timer.schedule(shutdownTimerTask, duration);
				}

				// Wait for a termination event.
				synchronized (m_eventSynchronisation) {
					while (!threadSynchronisation.isFinished()) {

						if (m_consoleListener.checkForMessage(ConsoleListener.ANY ^ ConsoleListener.START)) {
							break;
						}

						if (m_shutdownTriggered) {
							m_terminalLogger.info("Specified duration exceeded, Test is shut down");
							break;
						}

						m_eventSynchronisation.waitNoInterrruptException();
					}
				}

				synchronized (m_eventSynchronisation) {
					if (!threadSynchronisation.isFinished()) {

						m_terminalLogger.info("Waiting for threads to terminate");

						m_threadStarter = m_invalidThreadStarter;
						m_threadContexts.shutdownAll();

						// Interrupt any sleepers.
						SleeperImplementation.shutdownAllCurrentSleepers();

						final long time = System.currentTimeMillis();
						final long maximumShutdownTime = 10000;

						while (!threadSynchronisation.isFinished()) {
							if (System.currentTimeMillis() - time > maximumShutdownTime) {
								m_terminalLogger.info("Ignoring unresponsive threads");
								break;
							}

							m_eventSynchronisation.waitNoInterrruptException(maximumShutdownTime);
						}
					}
				}
			} finally {
				reportTimerTask.cancel();
				shutdownTimerTask.cancel();
			}

			scriptEngine.shutdown();

			// Final report to the console.
			reportTimerTask.run();

			if (!m_communicationShutdown) {
				sendStatusMessage(ProcessReport.STATE_FINISHED, (short) 0, (short) 0);
			}

			m_consoleSender.shutdown();

			final long elapsedTime = m_times.getElapsedTime();
			m_logger.info("elapsed time is {} ms", elapsedTime);

			m_logger.info("Final statistics for this process:");

			final StatisticsTable statisticsTable = new StatisticsTable(
					m_statisticsServices.getSummaryStatisticsView(),
					m_statisticsServices.getStatisticsIndexMap(), m_accumulatedStatistics);

			final StringWriter statistics = new StringWriter();
			statistics.write("\n");
			statisticsTable.print(new PrintWriter(statistics), elapsedTime);
			m_logger.info(statistics.toString());

			timer.cancel();

			m_terminalLogger.info("Finished");

		} catch (final ScriptExecutionException e) {
			m_logger.error("Aborting process - {}", e.getShortMessage(), e);
			m_terminalLogger.error("aborting process - {}", e.getShortMessage(), e);
		} catch (EngineException e) {
			m_logger.error("Script error - {}", e.getMessage(), e);
			throw e;
		}
	}
The application's main loop. This is split from the constructor as theoretically it might be
called multiple times. The constructor sets up the static configuration, this does a single
execution.
<p/>
<p>
This method is interruptible, in the same sense as
{@link net.grinder.util.thread.InterruptibleRunnable#interruptibleRun()}. We don't implement
that method because we want to be able to throw exceptions.
</p>

@throws net.grinder.common.GrinderException
         If something went wrong.
ngrinder-runtime/src/main/java/net/grinder/engine/process/GrinderProcess.java