Bug 1572337: Make GetRunningEventDelay handle threadpools r=froydnj

Threadpools run an event that then runs other events, so we need to tweak
things for GetRunningEventDelay()

Differential Revision: https://phabricator.services.mozilla.com/D44058

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Randell Jesup 2019-11-08 21:07:45 +00:00
parent 6cc1163786
commit 4be7858359
7 changed files with 116 additions and 12 deletions

View File

@ -405,6 +405,14 @@ LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
return NS_OK;
}
NS_IMETHODIMP
LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
if (mThread) {
return mThread->SetRunningEventDelay(aDelay, aStart);
}
return NS_OK;
}
NS_IMETHODIMP
LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) {
if (mThread) {

View File

@ -147,9 +147,14 @@ EventQueuePriority PrioritizedEventQueue::SelectQueue(
return queue;
}
// The delay returned is the queuing delay a hypothetical Input event would
// see due to the current running event if it had arrived while the current
// event was queued. This means that any event running at priority below
// Input doesn't cause queuing delay for Input events, and we return
// TimeDuration() for those cases.
already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock,
mozilla::TimeDuration* aLastEventDelay) {
mozilla::TimeDuration* aHypotheticalInputEventDelay) {
#ifndef RELEASE_OR_BETA
// Clear mNextIdleDeadline so that it is possible to determine that
// we're running an idle runnable in ProcessNextEvent.
@ -183,14 +188,16 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
break;
case EventQueuePriority::High:
event = mHighQueue->GetEvent(aPriority, aProofOfLock, aLastEventDelay);
event = mHighQueue->GetEvent(aPriority, aProofOfLock,
aHypotheticalInputEventDelay);
MOZ_ASSERT(event);
mInputHandlingStartTime = TimeStamp();
mProcessHighPriorityQueue = false;
break;
case EventQueuePriority::Input:
event = mInputQueue->GetEvent(aPriority, aProofOfLock, aLastEventDelay);
event = mInputQueue->GetEvent(aPriority, aProofOfLock,
aHypotheticalInputEventDelay);
MOZ_ASSERT(event);
break;
@ -200,17 +207,17 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
// an event actually runs (if the event is below Input event's priority)
case EventQueuePriority::MediumHigh:
event = mMediumHighQueue->GetEvent(aPriority, aProofOfLock);
*aLastEventDelay = TimeDuration();
*aHypotheticalInputEventDelay = TimeDuration();
break;
case EventQueuePriority::Normal:
event = mNormalQueue->GetEvent(aPriority, aProofOfLock);
*aLastEventDelay = TimeDuration();
*aHypotheticalInputEventDelay = TimeDuration();
break;
case EventQueuePriority::Idle:
case EventQueuePriority::DeferredTimers:
*aLastEventDelay = TimeDuration();
*aHypotheticalInputEventDelay = TimeDuration();
// If we get here, then all queues except deferredtimers and idle are
// empty.
@ -225,8 +232,7 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
return nullptr;
}
nsCOMPtr<nsIRunnable> event =
mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
event = mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
if (!event) {
event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
}
@ -243,6 +249,10 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
#endif
}
break;
} // switch (queue)
if (!event) {
*aHypotheticalInputEventDelay = TimeDuration();
}
return event.forget();

View File

@ -196,5 +196,18 @@ interface nsIThread : nsISerialEventTarget
*/
[noscript] void getRunningEventDelay(out TimeDuration delay, out TimeStamp start);
/**
* Set information on the timing of the currently-running event.
* Overrides the values returned by getRunningEventDelay
*
* @param delay
* Delay the running event spent in queues, or TimeDuration() if
* there's no running event.
* @param start
* The time the currently running event began to run, or TimeStamp() if no
* event is running.
*/
[noscript] void setRunningEventDelay(in TimeDuration delay, in TimeStamp start);
[noscript] void setNameForWakeupTelemetry(in ACString name);
};

View File

@ -601,6 +601,7 @@ nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
mShutdownRequired(false),
mPriority(PRIORITY_NORMAL),
mIsMainThread(aMainThread == MAIN_THREAD),
mIsAPoolThreadFree(nullptr),
mCanInvokeJS(false),
mCurrentEvent(nullptr),
mCurrentEventStart(TimeStamp::Now()),
@ -733,8 +734,22 @@ nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
NS_IMETHODIMP
nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
*aDelay = mLastEventDelay;
*aStart = mLastEventStart;
if (mIsAPoolThreadFree && *mIsAPoolThreadFree) {
// if there are unstarted threads in the pool, a new event to the
// pool would not be delayed at all (beyond thread start time)
*aDelay = TimeDuration();
*aStart = TimeStamp();
} else {
*aDelay = mLastEventDelay;
*aStart = mLastEventStart;
}
return NS_OK;
}
NS_IMETHODIMP
nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
mLastEventDelay = aDelay;
mLastEventStart = aStart;
return NS_OK;
}

View File

@ -14,6 +14,7 @@
#include "nsThreadUtils.h"
#include "nsString.h"
#include "nsTObserverArray.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
@ -89,6 +90,12 @@ class nsThread : public nsIThreadInternal,
// nsIThreadManager::NewThread.
bool ShutdownRequired() { return mShutdownRequired; }
// Lets GetRunningEventDelay() determine if the pool this is part
// of has an unstarted thread
void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) {
mIsAPoolThreadFree = aPtr;
}
void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver);
uint32_t RecursionDepth() const;
@ -228,6 +235,7 @@ class nsThread : public nsIThreadInternal,
int8_t mPriority;
bool mIsMainThread;
mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree;
// Set to true if this thread creates a JSRuntime.
bool mCanInvokeJS;

View File

@ -56,7 +56,8 @@ nsThreadPool::nsThreadPool()
mIdleCount(0),
mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE),
mShutdown(false),
mRegressiveMaxIdleTime(false) {
mRegressiveMaxIdleTime(false),
mIsAPoolThreadFree(true) {
static std::once_flag flag;
std::call_once(flag, [] { gCurrentThreadPool.infallibleInit(); });
@ -128,7 +129,11 @@ nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent,
killThread = true;
} else if (mThreads.Count() < (int32_t)mThreadLimit) {
mThreads.AppendObject(thread);
if (mThreads.Count() >= (int32_t)mThreadLimit) {
mIsAPoolThreadFree = false;
}
} else {
// Someone else may have also been starting a thread
killThread = true; // okay, we don't need this thread anymore
}
}
@ -162,6 +167,35 @@ void nsThreadPool::ShutdownThread(nsIThread* aThread) {
&nsIThread::AsyncShutdown));
}
// This event 'runs' for the lifetime of the worker thread. The actual
// eventqueue is mEvents, and is shared by all the worker threads. This
// means that the set of threads together define the delay seen by a new
// event sent to the pool.
//
// To model the delay experienced by the pool, we can have each thread in
// the pool report 0 if it's idle OR if the pool is below the threadlimit;
// or otherwise the current event's queuing delay plus current running
// time.
//
// To reconstruct the delays for the pool, the profiler can look at all the
// threads that are part of a pool (pools have defined naming patterns that
// can be user to connect them). If all threads have delays at time X,
// that means that all threads saturated at that point and any event
// dispatched to the pool would get a delay.
//
// The delay experienced by an event dispatched when all pool threads are
// busy is based on the calculations shown in platform.cpp. Run that
// algorithm for each thread in the pool, and the delay at time X is the
// longest value for time X of any of the threads, OR the time from X until
// any one of the threads reports 0 (i.e. it's not busy), whichever is
// shorter.
// In order to record this when the profiler samples threads in the pool,
// each thread must (effectively) override GetRunnningEventDelay, by
// resetting the mLastEventDelay/Start values in the nsThread when we start
// to run an event (or when we run out of events to run). Note that handling
// the shutdown of a thread may be a little tricky.
NS_IMETHODIMP
nsThreadPool::Run() {
LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading()));
@ -174,6 +208,10 @@ nsThreadPool::Run() {
bool wasIdle = false;
TimeStamp idleSince;
// This thread is an nsThread created below with NS_NewNamedThread()
static_cast<nsThread*>(current.get())
->SetPoolThreadFreePtr(&mIsAPoolThreadFree);
nsCOMPtr<nsIThreadPoolListener> listener;
{
MutexAutoLock lock(mMutex);
@ -189,10 +227,11 @@ nsThreadPool::Run() {
do {
nsCOMPtr<nsIRunnable> event;
TimeDuration delay;
{
MutexAutoLock lock(mMutex);
event = mEvents.GetEvent(nullptr, lock);
event = mEvents.GetEvent(nullptr, lock, &delay);
if (!event) {
TimeStamp now = TimeStamp::Now();
uint32_t idleTimeoutDivider =
@ -228,7 +267,12 @@ nsThreadPool::Run() {
--mIdleCount;
}
shutdownThreadOnExit = mThreads.RemoveObject(current);
// keep track if there are threads available to start
mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit);
} else {
current->SetRunningEventDelay(TimeDuration(), TimeStamp());
AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE);
TimeDuration delta = timeout - (now - idleSince);
@ -253,6 +297,10 @@ nsThreadPool::Run() {
// to run.
DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
// We'll handle the case of unstarted threads available
// when we sample.
current->SetRunningEventDelay(delay, TimeStamp::Now());
event->Run();
}
} while (!exitThread);

View File

@ -13,6 +13,7 @@
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsThreadUtils.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/EventQueue.h"
@ -47,6 +48,7 @@ class nsThreadPool final : public nsIThreadPool, public nsIRunnable {
nsCOMPtr<nsIThreadPoolListener> mListener;
bool mShutdown;
bool mRegressiveMaxIdleTime;
mozilla::Atomic<bool, mozilla::Relaxed> mIsAPoolThreadFree;
nsCString mName;
nsThreadPoolNaming mThreadNaming;
};