// Copyright (C) 2000 Paco Gomez // Copyright (C) 2000 - 2012 Philip Aston // 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.util.ArrayList; import java.util.List; import net.grinder.common.GrinderProperties; import net.grinder.common.SSLContextFactory; import net.grinder.common.SkeletonThreadLifeCycleListener; import net.grinder.common.Test; import net.grinder.common.ThreadLifeCycleListener; import net.grinder.engine.common.EngineException; import net.grinder.engine.process.DispatchContext.DispatchStateException; import net.grinder.script.Statistics.StatisticsForTest; import net.grinder.statistics.StatisticsServices; import net.grinder.statistics.StatisticsSet; import net.grinder.util.ListenerSupport; import net.grinder.util.ListenerSupport.Informer; import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.MarkerFactory; /** * Package scope. * * @author Philip Aston */ final class ThreadContextImplementation implements ThreadContext { private final ListenerSupport<ThreadLifeCycleListener> m_threadLifeCycleListeners = new ListenerSupport<ThreadLifeCycleListener>(); private final DispatchContextStack m_dispatchContextStack = new DispatchContextStack(); private final int m_threadNumber; private final Marker m_threadMarker; private final DispatchResultReporter m_dispatchResultReporter; private SSLContextFactory m_sslContextFactory; private boolean m_delayReports; private DispatchContext m_pendingDispatchContext; private StatisticsForTest m_statisticsForLastTest; private Marker m_runMarker; private int m_runNumber = -1; private Marker m_testMarker; private volatile boolean m_shutdown; private boolean m_shutdownReported; public ThreadContextImplementation(GrinderProperties properties, StatisticsServices statisticsServices, int threadNumber, Logger dataLogger) throws EngineException { m_threadNumber = threadNumber; m_threadMarker = MarkerFactory.getMarker("thread-" + threadNumber); // Undocumented property. Added so Tom Barnes can investigate overhead // of data logging. if (properties.getBoolean("grinder.logData", true)) { final ThreadDataLogger threadDataLogger = new ThreadDataLogger( dataLogger, statisticsServices.getDetailStatisticsView() .getExpressionViews(), m_threadNumber); m_dispatchResultReporter = new DispatchResultReporter() { public void report(Test test, long startTime, StatisticsSet statistics) { threadDataLogger.report(getRunNumber(), test, startTime, statistics); } }; } else { m_dispatchResultReporter = new DispatchResultReporter() { public void report(Test test, long startTime, StatisticsSet statistics) { // Null reporter. } }; } registerThreadLifeCycleListener(new SkeletonThreadLifeCycleListener() { public void endRun() { reportPendingDispatchContext(); } }); } public int getThreadNumber() { return m_threadNumber; } public int getRunNumber() { return m_runNumber; } @Override public void setCurrentRunNumber(int run) { if (m_runMarker != null) { m_threadMarker.remove(m_runMarker); MarkerFactory.getIMarkerFactory().detachMarker( m_runMarker.getName()); } if (run != -1) { m_runMarker = MarkerFactory.getMarker("run-" + run); m_threadMarker.add(m_runMarker); } m_runNumber = run; } /** Package scope for unit tests. */ void setTestLogMarker(Marker marker) { if (m_testMarker != null) { m_threadMarker.remove(m_testMarker); } m_testMarker = marker; if (marker != null) { m_threadMarker.add(marker); } } public SSLContextFactory getThreadSSLContextFactory() { return m_sslContextFactory; } public void setThreadSSLContextFactory(SSLContextFactory sslContextFactory) { m_sslContextFactory = sslContextFactory; } public DispatchResultReporter getDispatchResultReporter() { return m_dispatchResultReporter; } public void registerThreadLifeCycleListener(ThreadLifeCycleListener listener) { m_threadLifeCycleListeners.add(listener); } public void removeThreadLifeCycleListener(ThreadLifeCycleListener listener) { m_threadLifeCycleListeners.remove(listener); } public void fireBeginThreadEvent() { m_threadLifeCycleListeners .apply(new Informer<ThreadLifeCycleListener>() { public void inform(ThreadLifeCycleListener l) { l.beginThread(); } }); } public void fireBeginRunEvent() { m_threadLifeCycleListeners .apply(new Informer<ThreadLifeCycleListener>() { public void inform(ThreadLifeCycleListener l) { l.beginRun(); } }); } public void fireEndRunEvent() { m_threadLifeCycleListeners .apply(new Informer<ThreadLifeCycleListener>() { public void inform(ThreadLifeCycleListener l) { l.endRun(); } }); } public void fireBeginShutdownEvent() { m_threadLifeCycleListeners .apply(new Informer<ThreadLifeCycleListener>() { public void inform(ThreadLifeCycleListener l) { l.beginShutdown(); } }); } public void fireEndThreadEvent() { m_threadLifeCycleListeners .apply(new Informer<ThreadLifeCycleListener>() { public void inform(ThreadLifeCycleListener l) { l.endThread(); } }); } public void pushDispatchContext(DispatchContext dispatchContext) throws ShutdownException { if (m_shutdown) { // As soon as we're shutdown, we disable the instrumentation. This // avoids reporting of misleading test failures. // We only throw ShutdownException from pushDispatchContext. A test // that // was in-flight at the time of shutdown will complete normally // unless // the thread attempts to start a nested test. m_shutdownReported = true; throw new ShutdownException("Thread has been shut down"); } reportPendingDispatchContext(); setTestLogMarker(dispatchContext.getLogMarker()); final DispatchContext existingContext = m_dispatchContextStack .peekTop(); if (existingContext != null) { existingContext.setHasNestedContexts(); } m_dispatchContextStack.push(dispatchContext); } public void popDispatchContext() { if (m_shutdownReported) { return; } final DispatchContext dispatchContext = m_dispatchContextStack.pop(); if (dispatchContext == null) { throw new AssertionError("DispatchContext stack unexpectedly empty"); } final DispatchContext parentDispatchContext = m_dispatchContextStack .peekTop(); if (parentDispatchContext != null) { parentDispatchContext.getPauseTimer().add( dispatchContext.getPauseTimer()); } m_statisticsForLastTest = dispatchContext.getStatisticsForTest(); // Flush any pending report created by an inner test. reportPendingDispatchContext(); if (m_delayReports) { m_pendingDispatchContext = dispatchContext; } else { try { dispatchContext.report(); } catch (DispatchStateException e) { throw new AssertionError(e); } } setTestLogMarker(null); } public StatisticsForTest getStatisticsForCurrentTest() { final DispatchContext dispatchContext = m_dispatchContextStack .peekTop(); if (dispatchContext == null) { return null; } return dispatchContext.getStatisticsForTest(); } public StatisticsForTest getStatisticsForLastTest() { return m_statisticsForLastTest; } public void setDelayReports(boolean b) { if (!b) { reportPendingDispatchContext(); } m_delayReports = b; } public void reportPendingDispatchContext() { if (m_pendingDispatchContext != null) { try { m_pendingDispatchContext.report(); } catch (DispatchStateException e) { throw new AssertionError(e); } m_pendingDispatchContext = null; } } public void pauseClock() { final DispatchContext dispatchContext = m_dispatchContextStack .peekTop(); if (dispatchContext != null) { dispatchContext.getPauseTimer().start(); } } public void resumeClock() { final DispatchContext dispatchContext = m_dispatchContextStack .peekTop(); if (dispatchContext != null) { dispatchContext.getPauseTimer().stop(); } } private static final class DispatchContextStack { private final List<DispatchContext> m_stack = new ArrayList<DispatchContext>(); public void push(DispatchContext dispatchContext) { m_stack.add(dispatchContext); } public DispatchContext pop() { final int size = m_stack.size(); if (size == 0) { return null; } return m_stack.remove(size - 1); } public DispatchContext peekTop() { final int size = m_stack.size(); if (size == 0) { return null; } return m_stack.get(size - 1); } } public void shutdown() { MarkerFactory.getIMarkerFactory() .detachMarker(m_threadMarker.getName()); m_shutdown = true; } @Override public Marker getLogMarker() { return m_threadMarker; } }