diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index fbbc5879139b..04f29e4b658b 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -87,6 +87,7 @@ #include "nsCycleCollectionNoteRootCallback.h" #include "GeckoProfiler.h" +#include "mozilla/IdleTaskRunner.h" using namespace mozilla; using namespace mozilla::dom; @@ -120,7 +121,7 @@ const size_t gStackSize = 8192; #define NS_CC_SKIPPABLE_DELAY 250 // ms // ForgetSkippable is usually fast, so we can use small budgets. -// This isn't a real budget but a hint to CollectorRunner whether there +// This isn't a real budget but a hint to IdleTaskRunner whether there // is enough time to call ForgetSkippable. static const int64_t kForgetSkippableSliceDuration = 2; @@ -149,16 +150,14 @@ static const uint32_t kMaxICCDuration = 2000; // ms // Large value used to specify that a script should run essentially forever #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32) -class CollectorRunner; - // if you add statics here, add them to the list in StartupJSEnvironment static nsITimer *sGCTimer; static nsITimer *sShrinkingGCTimer; -static StaticRefPtr sCCRunner; -static StaticRefPtr sICCRunner; +static StaticRefPtr sCCRunner; +static StaticRefPtr sICCRunner; static nsITimer *sFullGCTimer; -static StaticRefPtr sInterSliceGCRunner; +static StaticRefPtr sInterSliceGCRunner; static TimeStamp sLastCCEndTime; @@ -229,184 +228,6 @@ static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5; static TimeDuration sGCUnnotifiedTotalTime; -// Return true if some meaningful work was done. -typedef bool (*CollectorRunnerCallback) (TimeStamp aDeadline, void* aData); - -// Repeating callback runner for CC and GC. -class CollectorRunner final : public IdleRunnable -{ -public: - static already_AddRefed - Create(CollectorRunnerCallback aCallback, uint32_t aDelay, - int64_t aBudget, bool aRepeating, void* aData = nullptr) - { - if (sShuttingDown) { - return nullptr; - } - - RefPtr runner = - new CollectorRunner(aCallback, aDelay, aBudget, aRepeating, aData); - runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. - return runner.forget(); - } - - NS_IMETHOD Run() override - { - if (!mCallback) { - return NS_OK; - } - - // Deadline is null when called from timer. - bool deadLineWasNull = mDeadline.IsNull(); - bool didRun = false; - if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) { - CancelTimer(); - didRun = mCallback(mDeadline, mData); - } - - if (mCallback && (mRepeating || !didRun)) { - // If we didn't do meaningful work, don't schedule using immediate - // idle dispatch, since that could lead to a loop until the idle - // period ends. - Schedule(didRun); - } - - return NS_OK; - } - - static void - TimedOut(nsITimer* aTimer, void* aClosure) - { - RefPtr runnable = static_cast(aClosure); - runnable->Run(); - } - - void SetDeadline(mozilla::TimeStamp aDeadline) override - { - mDeadline = aDeadline; - }; - - void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override - { - if (mTimerActive) { - return; - } - - mTarget = aTarget; - if (!mTimer) { - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - } else { - mTimer->Cancel(); - } - - if (mTimer) { - mTimer->SetTarget(mTarget); - mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay, - nsITimer::TYPE_ONE_SHOT, - "CollectorRunner"); - mTimerActive = true; - } - } - - nsresult Cancel() override - { - CancelTimer(); - mTimer = nullptr; - mScheduleTimer = nullptr; - mCallback = nullptr; - return NS_OK; - } - - static void - ScheduleTimedOut(nsITimer* aTimer, void* aClosure) - { - RefPtr runnable = static_cast(aClosure); - runnable->Schedule(true); - } - - void Schedule(bool aAllowIdleDispatch) - { - if (!mCallback) { - return; - } - - if (sShuttingDown) { - Cancel(); - return; - } - - mDeadline = TimeStamp(); - TimeStamp now = TimeStamp::Now(); - TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now); - if (hint != now) { - // RefreshDriver is ticking, let it schedule the idle dispatch. - nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay); - // Ensure we get called at some point, even if RefreshDriver is stopped. - SetTimer(mDelay, mTarget); - } else { - // RefreshDriver doesn't seem to be running. - if (aAllowIdleDispatch) { - nsCOMPtr runnable = this; - NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay); - SetTimer(mDelay, mTarget); - } else { - if (!mScheduleTimer) { - mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - if (!mScheduleTimer) { - return; - } - } else { - mScheduleTimer->Cancel(); - } - - // We weren't allowed to do idle dispatch immediately, do it after a - // short timeout. - mScheduleTimer->InitWithNamedFuncCallback(ScheduleTimedOut, this, 16, - nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, - "CollectorRunner"); - } - } - } - -private: - explicit CollectorRunner(CollectorRunnerCallback aCallback, - uint32_t aDelay, int64_t aBudget, - bool aRepeating, void* aData) - : mCallback(aCallback), mDelay(aDelay) - , mBudget(TimeDuration::FromMilliseconds(aBudget)) - , mRepeating(aRepeating), mTimerActive(false), mData(aData) - { - } - - ~CollectorRunner() - { - CancelTimer(); - } - - void CancelTimer() - { - nsRefreshDriver::CancelIdleRunnable(this); - if (mTimer) { - mTimer->Cancel(); - } - if (mScheduleTimer) { - mScheduleTimer->Cancel(); - } - mTimerActive = false; - } - - nsCOMPtr mTimer; - nsCOMPtr mScheduleTimer; - nsCOMPtr mTarget; - CollectorRunnerCallback mCallback; - uint32_t mDelay; - TimeStamp mDeadline; - TimeDuration mBudget; - bool mRepeating; - bool mTimerActive; - void* mData; -}; - static const char* ProcessNameForCollectorLog() { diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 000000000000..19e89e2ce4a8 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IdleTaskRunner.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +already_AddRefed +IdleTaskRunner::Create(IdleTaskRunnerCallback aCallback, uint32_t aDelay, + int64_t aBudget, bool aRepeating, void* aData) +{ + if (sShuttingDown) { + return nullptr; + } + + RefPtr runner = + new IdleTaskRunner(aCallback, aDelay, aBudget, aRepeating, aData); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +IdleTaskRunner::IdleTaskRunner(IdleTaskRunnerCallback aCallback, + uint32_t aDelay, int64_t aBudget, + bool aRepeating, void* aData) + : mCallback(aCallback), mDelay(aDelay) + , mBudget(TimeDuration::FromMilliseconds(aBudget)) + , mRepeating(aRepeating), mTimerActive(false), mData(aData) +{ +} + +NS_IMETHODIMP +IdleTaskRunner::Run() +{ + if (!mCallback) { + return NS_OK; + } + + // Deadline is null when called from timer. + bool deadLineWasNull = mDeadline.IsNull(); + bool didRun = false; + if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) { + CancelTimer(); + didRun = mCallback(mDeadline, mData); + } + + if (mCallback && (mRepeating || !didRun)) { + // If we didn't do meaningful work, don't schedule using immediate + // idle dispatch, since that could lead to a loop until the idle + // period ends. + Schedule(didRun); + } + + return NS_OK; +} + +static void +TimedOut(nsITimer* aTimer, void* aClosure) +{ + RefPtr runnable = static_cast(aClosure); + runnable->Run(); +} + +void +IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline) +{ + mDeadline = aDeadline; +}; + +void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) +{ + if (mTimerActive) { + return; + } + mTarget = aTarget; + if (!mTimer) { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->SetTarget(mTarget); + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT); + mTimerActive = true; + } +} + +nsresult +IdleTaskRunner::Cancel() +{ + CancelTimer(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; + return NS_OK; +} + +static void +ScheduleTimedOut(nsITimer* aTimer, void* aClosure) +{ + RefPtr runnable = static_cast(aClosure); + runnable->Schedule(true); +} + +void +IdleTaskRunner::Schedule(bool aAllowIdleDispatch) +{ + if (!mCallback) { + return; + } + + if (sShuttingDown) { + Cancel(); + return; + } + + mDeadline = TimeStamp(); + TimeStamp now = TimeStamp::Now(); + TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now); + if (hint != now) { + // RefreshDriver is ticking, let it schedule the idle dispatch. + nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay); + // Ensure we get called at some point, even if RefreshDriver is stopped. + SetTimer(mDelay, mTarget); + } else { + // RefreshDriver doesn't seem to be running. + if (aAllowIdleDispatch) { + nsCOMPtr runnable = this; + NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay); + SetTimer(mDelay, mTarget); + } else { + if (!mScheduleTimer) { + mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (!mScheduleTimer) { + return; + } + } else { + mScheduleTimer->Cancel(); + } + + // We weren't allowed to do idle dispatch immediately, do it after a + // short timeout. + mScheduleTimer->InitWithNamedFuncCallback(ScheduleTimedOut, this, 16, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + } + } +} + +IdleTaskRunner::~IdleTaskRunner() +{ + CancelTimer(); +} + +void +IdleTaskRunner::CancelTimer() +{ + nsRefreshDriver::CancelIdleRunnable(this); + if (mTimer) { + mTimer->Cancel(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; +} + +} // end of namespace mozilla diff --git a/xpcom/threads/IdleTaskRunner.h b/xpcom/threads/IdleTaskRunner.h new file mode 100644 index 000000000000..604aa99e8c07 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IdleTaskRunner_h +#define IdleTaskRunner_h + +#include "nsThreadUtils.h" + +namespace mozilla { + +// Return true if some meaningful work was done. +typedef bool (*IdleTaskRunnerCallback) (TimeStamp aDeadline, void* aData); + +// Repeating callback runner for CC and GC. +class IdleTaskRunner final : public IdleRunnable +{ +public: + static already_AddRefed + Create(IdleTaskRunnerCallback aCallback, uint32_t aDelay, + int64_t aBudget, bool aRepeating, void* aData = nullptr); + + NS_IMETHOD Run() override; + + void SetDeadline(mozilla::TimeStamp aDeadline) override; + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override; + + nsresult Cancel() override; + void Schedule(bool aAllowIdleDispatch); + +private: + explicit IdleTaskRunner(IdleTaskRunnerCallback aCallback, + uint32_t aDelay, int64_t aBudget, + bool aRepeating, void* aData); + ~IdleTaskRunner(); + void CancelTimer(); + + nsCOMPtr mTimer; + nsCOMPtr mScheduleTimer; + nsCOMPtr mTarget; + IdleTaskRunnerCallback mCallback; + uint32_t mDelay; + TimeStamp mDeadline; + TimeDuration mBudget; + bool mRepeating; + bool mTimerActive; + void* mData; +}; + +} // end of unnamed namespace. + +#endif diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index b619062c732a..2085dbbb2062 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -42,6 +42,7 @@ EXPORTS.mozilla += [ 'DeadlockDetector.h', 'HangAnnotations.h', 'HangMonitor.h', + 'IdleTaskRunner.h', 'LazyIdleThread.h', 'MainThreadIdlePeriod.h', 'Monitor.h', @@ -67,6 +68,7 @@ UNIFIED_SOURCES += [ 'BlockingResourceBase.cpp', 'HangAnnotations.cpp', 'HangMonitor.cpp', + 'IdleTaskRunner.cpp', 'LazyIdleThread.cpp', 'MainThreadIdlePeriod.cpp', 'nsEnvironment.cpp',