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

net.grinder.engine.process.GrinderProcess Maven / Gradle / Ivy

There is a newer version: 3.11
Show newest version
// Copyright (C) 2000 Paco Gomez
// Copyright (C) 2000 - 2012 Philip Aston
// Copyright (C) 2003 Kalyanaraman Venkatasubramaniy
// Copyright (C) 2004 Slavik Gnatenko
// All rights reserved.
//
// This file is part of The Grinder software distribution. Refer to
// the file LICENSE which is part of The Grinder distribution for
// licensing details. The Grinder distribution is available on the
// Internet at http://grinder.sourceforge.net/
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package net.grinder.engine.process;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import net.grinder.common.GrinderBuild;
import net.grinder.common.GrinderException;
import net.grinder.common.GrinderProperties;
import net.grinder.common.SkeletonThreadLifeCycleListener;
import net.grinder.common.Test;
import net.grinder.common.processidentity.ProcessReport;
import net.grinder.common.processidentity.ProcessReport.State;
import net.grinder.common.processidentity.WorkerIdentity;
import net.grinder.communication.ClientSender;
import net.grinder.communication.CommunicationException;
import net.grinder.communication.ConnectionType;
import net.grinder.communication.Message;
import net.grinder.communication.MessageDispatchSender;
import net.grinder.communication.MessagePump;
import net.grinder.communication.QueuedSender;
import net.grinder.communication.QueuedSenderDecorator;
import net.grinder.communication.Receiver;
import net.grinder.engine.common.ConnectorFactory;
import net.grinder.engine.common.EngineException;
import net.grinder.engine.communication.ConsoleListener;
import net.grinder.engine.messages.InitialiseGrinderMessage;
import net.grinder.engine.process.dcr.DCRContextImplementation;
import net.grinder.messages.console.RegisterTestsMessage;
import net.grinder.messages.console.ReportStatisticsMessage;
import net.grinder.messages.console.WorkerAddress;
import net.grinder.messages.console.WorkerProcessReportMessage;
import net.grinder.script.Grinder;
import net.grinder.script.InternalScriptContext;
import net.grinder.script.InvalidContextException;
import net.grinder.script.Statistics;
import net.grinder.scriptengine.Instrumenter;
import net.grinder.scriptengine.ScriptEngineService.ScriptEngine;
import net.grinder.scriptengine.ScriptEngineService.WorkerRunnable;
import net.grinder.statistics.ExpressionView;
import net.grinder.statistics.StatisticsServices;
import net.grinder.statistics.StatisticsServicesImplementation;
import net.grinder.statistics.StatisticsTable;
import net.grinder.statistics.TestStatisticsMap;
import net.grinder.synchronisation.BarrierGroups;
import net.grinder.synchronisation.BarrierIdentityGenerator;
import net.grinder.synchronisation.ClientBarrierGroups;
import net.grinder.synchronisation.LocalBarrierGroups;
import net.grinder.util.JVM;
import net.grinder.util.ListenerSupport;
import net.grinder.util.ListenerSupport.Informer;
import net.grinder.util.Sleeper;
import net.grinder.util.SleeperImplementation;
import net.grinder.util.StandardTimeAuthority;
import net.grinder.util.TimeAuthority;
import net.grinder.util.thread.BooleanCondition;
import net.grinder.util.thread.Condition;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.spi.JoranException;


/**
 * The controller for a worker process.
 *
 * 

Package scope.

* * @author Paco Gomez * @author Philip Aston * @see net.grinder.engine.process.GrinderThread */ final class GrinderProcess { private final Logger m_terminalLogger; private final Logger m_logger; private final Logger m_dataLogger; private final boolean m_reportTimesToConsole; private final QueuedSender m_consoleSender; private final Sleeper m_sleeper; private final InitialiseGrinderMessage m_initialisationMessage; private final ConsoleListener m_consoleListener; private final StatisticsServices m_statisticsServices; private final TestStatisticsMap m_accumulatedStatistics; private final TestStatisticsHelperImplementation m_testStatisticsHelper; private final TestRegistryImplementation m_testRegistryImplementation; private final Condition m_eventSynchronisation = new Condition(); private final MessagePump m_messagePump; private final ThreadStarter m_invalidThreadStarter = new InvalidThreadStarter(); private final Times m_times = new Times(); private final ThreadContexts m_threadContexts = new ThreadContexts(); private final ListenerSupport m_processLifeCycleListeners = new ListenerSupport(); // Guarded by m_eventSynchronisation. private ThreadStarter m_threadStarter = m_invalidThreadStarter; private boolean m_shutdownTriggered; private boolean m_communicationShutdown; /** * Creates a new GrinderProcess instance. * * @param agentReceiver * Receiver used to listen to the agent. * @exception GrinderException * If the process could not be created. */ public GrinderProcess(Receiver agentReceiver) throws GrinderException { m_initialisationMessage = (InitialiseGrinderMessage)agentReceiver.waitForMessage(); if (m_initialisationMessage == null) { throw new EngineException("No control stream from agent"); } final GrinderProperties properties = m_initialisationMessage.getProperties(); final WorkerIdentity workerIdentity = m_initialisationMessage.getWorkerIdentity(); final String workerName = workerIdentity.getName(); final String logDirectory = properties.getProperty(GrinderProperties.LOG_DIRECTORY, "."); m_terminalLogger = LoggerFactory.getLogger(workerName); m_reportTimesToConsole = properties.getBoolean("grinder.reportTimesToConsole", true); configureLogging(workerName, logDirectory); m_logger = LoggerFactory.getLogger("worker." + workerName); m_dataLogger = LoggerFactory.getLogger("data"); m_logger.info("The Grinder version {}", GrinderBuild.getVersionString()); m_logger.info(JVM.getInstance().toString()); m_logger.info("time zone is {}", new SimpleDateFormat("z (Z)").format(new Date())); final MessageDispatchSender messageDispatcher = new MessageDispatchSender(); final BarrierGroups barrierGroups; if (m_initialisationMessage.getReportToConsole()) { m_consoleSender = new QueuedSenderDecorator( ClientSender.connect( new ConnectorFactory(ConnectionType.WORKER).create(properties), new WorkerAddress(workerIdentity))); barrierGroups = new ClientBarrierGroups(m_consoleSender, messageDispatcher); } else { m_consoleSender = new NullQueuedSender(); barrierGroups = new LocalBarrierGroups(); } final BarrierIdentityGenerator barrierIdentityGenerator = new BarrierIdentityGenerator(m_initialisationMessage.getWorkerIdentity()); final ThreadStarter delegatingThreadStarter = new ThreadStarter() { public int startThread(Object testRunner) throws EngineException, InvalidContextException { final ThreadStarter threadStarter; synchronized (m_eventSynchronisation) { threadStarter = m_threadStarter; } return threadStarter.startThread(testRunner); } }; m_statisticsServices = StatisticsServicesImplementation.getInstance(); m_accumulatedStatistics = new TestStatisticsMap(m_statisticsServices.getStatisticsSetFactory()); m_testStatisticsHelper = new TestStatisticsHelperImplementation( m_statisticsServices.getStatisticsIndexMap()); m_testRegistryImplementation = new TestRegistryImplementation( m_threadContexts, m_statisticsServices.getStatisticsSetFactory(), m_testStatisticsHelper, m_times.getTimeAuthority()); final Logger externalLogger = new ExternalLogger(m_logger, m_threadContexts); m_sleeper = new SleeperImplementation( m_times.getTimeAuthority(), externalLogger, properties.getDouble("grinder.sleepTimeFactor", 1.0d), properties.getDouble("grinder.sleepTimeVariation", 0.2d)); final Statistics scriptStatistics = new ScriptStatisticsImplementation(m_threadContexts, m_statisticsServices, m_consoleSender); final ThreadStopper threadStopper = new ThreadStopper() { public boolean stopThread(int threadNumber) { return m_threadContexts.shutdown(threadNumber); } }; final InternalScriptContext scriptContext = new ScriptContextImplementation( workerIdentity, m_initialisationMessage.getFirstWorkerIdentity(), m_threadContexts, properties, externalLogger, m_sleeper, new SSLControlImplementation(m_threadContexts), scriptStatistics, m_testRegistryImplementation, delegatingThreadStarter, threadStopper, barrierGroups, barrierIdentityGenerator); Grinder.grinder = scriptContext; final PluginRegistryImplementation pluginRegistry = new PluginRegistryImplementation(externalLogger, scriptContext, m_threadContexts, m_statisticsServices, m_times.getTimeAuthority()); m_processLifeCycleListeners.add(pluginRegistry); m_processLifeCycleListeners.add(m_threadContexts); // If we don't call getLocalHost() before spawning our // ConsoleListener thread, any attempt to call it afterwards will // silently crash the JVM. Reproduced with both J2SE 1.3.1-b02 and // J2SE 1.4.1_03-b02 on W2K. Do not ask me why, I've stopped // caring. try { java.net.InetAddress.getLocalHost(); } catch (UnknownHostException e) { /* Ignore */ } m_consoleListener = new ConsoleListener(m_eventSynchronisation, m_logger); m_consoleListener.registerMessageHandlers(messageDispatcher); m_messagePump = new MessagePump(agentReceiver, messageDispatcher, 1); } private static void configureLogging(String workerName, String logDirectory) throws EngineException { final Context context = (Context) LoggerFactory.getILoggerFactory(); final JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); context.putProperty("WORKER_NAME", workerName); context.putProperty("LOG_DIRECTORY", logDirectory); try { configurator.doConfigure( GrinderProcess.class.getResource("/logback-worker.xml")); } catch (JoranException e) { throw new EngineException("Could not initialise logger", 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. * *

* 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. *

* * @throws GrinderException * If something went wrong. */ public void run() throws GrinderException { 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 (int i = 0; i < detailExpressionViews.length; ++i) { dataLogHeader.append(", "); dataLogHeader.append(detailExpressionViews[i].getDisplayName()); } m_dataLogger.info(dataLogHeader.toString()); sendStatusMessage(ProcessReport.State.STARTED, (short)0, numberOfThreads); final ThreadSynchronisation threadSynchronisation = 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("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, shutting 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"); } public void shutdown(boolean inputStreamIsStdin) { if (!inputStreamIsStdin) { // Sadly it appears its impossible to interrupt a read() on a process // input stream (at least under W2K), so we can't shut down the message // pump cleanly. It runs in a daemon thread, so this isn't a big // deal. m_messagePump.shutdown(); } // Logback doesn't stop its loggers on exit (see LBCORE-202). We do // so explicitly to flush our BufferedEchoMessageEncoder. ((LoggerContext) LoggerFactory.getILoggerFactory()).stop(); } private class ReportToConsoleTimerTask extends TimerTask { private final ThreadSynchronisation m_threads; public ReportToConsoleTimerTask(ThreadSynchronisation threads) { m_threads = threads; } public void run() { if (!m_communicationShutdown) { try { final TestStatisticsMap sample = m_testRegistryImplementation.getTestStatisticsMap().reset(); m_accumulatedStatistics.add(sample); // We look up the new tests after we've taken the sample to // avoid a race condition when new tests are being added. final Collection newTests = m_testRegistryImplementation.getNewTests(); if (newTests != null) { m_consoleSender.send(new RegisterTestsMessage(newTests)); } if (sample.size() > 0) { if (!m_reportTimesToConsole) { m_testStatisticsHelper.removeTestTimeFromSample(sample); } m_consoleSender.send(new ReportStatisticsMessage(sample)); } sendStatusMessage(ProcessReport.State.RUNNING, m_threads.getNumberOfRunningThreads(), m_threads.getTotalNumberOfThreads()); } catch (CommunicationException e) { m_terminalLogger.info("Report to console failed", e); m_communicationShutdown = true; } } } } private void sendStatusMessage(State finished, short numberOfThreads, short totalNumberOfThreads) throws CommunicationException { m_consoleSender.send(new WorkerProcessReportMessage( finished, numberOfThreads, totalNumberOfThreads)); m_consoleSender.flush(); } private class ShutdownTimerTask extends TimerTask { public void run() { synchronized (m_eventSynchronisation) { m_shutdownTriggered = true; m_eventSynchronisation.notifyAll(); } } } /** * Implement {@link WorkerThreadSynchronisation}. I looked hard at JSR 166's * CountDownLatch and CyclicBarrier, but neither * of them allow for the waiting thread to be interrupted by other events. * *

Package scope for unit tests.

*/ static class ThreadSynchronisation implements WorkerThreadSynchronisation { private final BooleanCondition m_started = new BooleanCondition(); private final Condition m_threadEventCondition; private short m_numberCreated = 0; private short m_numberAwaitingStart = 0; private short m_numberFinished = 0; ThreadSynchronisation(Condition condition) { m_threadEventCondition = condition; } /** * The number of worker threads that have been created but not run to * completion. */ public short getNumberOfRunningThreads() { synchronized (m_threadEventCondition) { return (short)(m_numberCreated - m_numberFinished); } } public boolean isReadyToStart() { synchronized (m_threadEventCondition) { return m_numberAwaitingStart >= getNumberOfRunningThreads(); } } public boolean isFinished() { return getNumberOfRunningThreads() <= 0; } /** * The number of worker threads that have been created. */ public short getTotalNumberOfThreads() { synchronized (m_threadEventCondition) { return m_numberCreated; } } public void threadCreated() { synchronized (m_threadEventCondition) { ++m_numberCreated; } } public void startThreads() { synchronized (m_threadEventCondition) { while (!isReadyToStart()) { m_threadEventCondition.waitNoInterrruptException(); } m_numberAwaitingStart = 0; } m_started.set(true); } public void awaitStart() { synchronized (m_threadEventCondition) { ++m_numberAwaitingStart; if (isReadyToStart()) { m_threadEventCondition.notifyAll(); } } m_started.await(true); } public void threadFinished() { synchronized (m_threadEventCondition) { ++m_numberFinished; if (isReadyToStart() || isFinished()) { m_threadEventCondition.notifyAll(); } } } } private final class ThreadStarterImplementation implements ThreadStarter { private final ThreadSynchronisation m_threadSynchronisation; private final ScriptEngine m_scriptEngine; private final WorkerRunnableFactory m_defaultWorkerRunnableFactory; private final ProcessLifeCycleListener m_threadLifeCycleCallbacks = new ProcessLifeCycleListener() { public void threadCreated(final ThreadContext threadContext) { m_processLifeCycleListeners.apply( new Informer() { public void inform(ProcessLifeCycleListener listener) { listener.threadCreated(threadContext); } }); } public void threadStarted(final ThreadContext threadContext) { m_processLifeCycleListeners.apply( new Informer() { public void inform(ProcessLifeCycleListener listener) { listener.threadStarted(threadContext); } }); } }; private int m_i = -1; private ThreadStarterImplementation( ThreadSynchronisation threadSynchronisation, ScriptEngine scriptEngine) { m_threadSynchronisation = threadSynchronisation; m_scriptEngine = scriptEngine; m_defaultWorkerRunnableFactory = new WorkerRunnableFactory() { public WorkerRunnable create() throws EngineException { return m_scriptEngine.createWorkerRunnable(); } }; } public int startThread(final Object testRunner) throws EngineException { final int threadNumber; synchronized (this) { threadNumber = ++m_i; } final ThreadContext threadContext = new ThreadContextImplementation( m_initialisationMessage.getProperties(), m_statisticsServices, threadNumber, m_dataLogger); final WorkerRunnableFactory workerRunnableFactory; if (testRunner != null) { workerRunnableFactory = new WorkerRunnableFactory() { public WorkerRunnable create() throws EngineException { return m_scriptEngine.createWorkerRunnable(testRunner); } }; } else { workerRunnableFactory = m_defaultWorkerRunnableFactory; } final GrinderThread runnable = new GrinderThread(m_logger, threadContext, m_threadSynchronisation, m_threadLifeCycleCallbacks, m_initialisationMessage.getProperties(), m_sleeper, workerRunnableFactory); final Thread t = new Thread(runnable, "thread " + threadNumber); t.setDaemon(true); t.start(); return threadNumber; } } /** * Package scope for unit tests. */ static final class InvalidThreadStarter implements ThreadStarter { public int startThread(Object testRunner) throws InvalidContextException { throw new InvalidContextException( "You should not start worker threads until the main thread has " + "initialised the script engine, or after all other threads have " + "shut down. Typically, you should only call startWorkerThread() from " + "another worker thread."); } } /** * Package scope for unit tests. */ static final class Times { private volatile long m_executionStartTime; private final TimeAuthority m_timeAuthority = new StandardTimeAuthority(); /** * {@link GrinderProcess} calls {@link #setExecutionStartTime} just * before launching threads, after which it is never called again. */ public void setExecutionStartTime() { m_executionStartTime = m_timeAuthority.getTimeInMilliseconds(); } /** * {@link GrinderProcess} calls {@link #setExecutionStartTime} just before * launching threads, after which it is never called again. * * @return Start of execution, in milliseconds since the Epoch. */ public long getExecutionStartTime() { return m_executionStartTime; } /** * Elapsed time since execution was started. * * @return The time in milliseconds. * @see #getExecutionStartTime() */ public long getElapsedTime() { return m_timeAuthority.getTimeInMilliseconds() - getExecutionStartTime(); } public TimeAuthority getTimeAuthority() { return m_timeAuthority; } } /** * Package scope for unit tests. */ static final class ThreadContexts implements ProcessLifeCycleListener, ThreadContextLocator { private final ThreadLocal m_threadContextThreadLocal = new ThreadLocal(); // Guarded by self. private final Map m_threadContextsMap = new HashMap(); // Guarded by m_threadContextsMap. private boolean m_allShutdown; public ThreadContext get() { return m_threadContextThreadLocal.get(); } public void threadCreated(ThreadContext threadContext) { final Integer threadNumber = threadContext.getThreadNumber(); final boolean shutdown; synchronized (m_threadContextsMap) { shutdown = m_allShutdown; if (!shutdown) { threadContext.registerThreadLifeCycleListener( new SkeletonThreadLifeCycleListener() { public void endThread() { m_threadContextsMap.remove(threadNumber); } }); // Very unlikely, harmless race here - we could store a reference to // a thread context that is in the process of shutting down. m_threadContextsMap.put(threadNumber, threadContext); } } if (shutdown) { // Stop new threads in their tracks. threadContext.shutdown(); } } public void threadStarted(ThreadContext threadContext) { m_threadContextThreadLocal.set(threadContext); } public boolean shutdown(int threadNumber) { final ThreadContext threadContext; synchronized (m_threadContextsMap) { threadContext = m_threadContextsMap.get(threadNumber); } if (threadContext != null) { threadContext.shutdown(); return true; } return false; } public void shutdownAll() { final ThreadContext[] threadContexts; synchronized (m_threadContextsMap) { m_allShutdown = true; threadContexts = m_threadContextsMap.values().toArray( new ThreadContext[m_threadContextsMap.size()]); } for (int i = 0; i < threadContexts.length; ++i) { threadContexts[i].shutdown(); } } } /** * Package scope for unit tests. */ static final class NullQueuedSender implements QueuedSender { public void send(Message message) { } public void flush() { } public void shutdown() { } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy