Issue #2323 - Part 2: Implement timer nesting and clamping for workers.

This commit is contained in:
Moonchild 2023-10-01 19:38:52 +02:00 • committed by roytam1
commit 1b7df316c4
2 changed files with 54 additions and 3 deletions

View file

@ -167,6 +167,9 @@ const nsIID kDEBUGWorkerEventTargetIID = {
#endif
// The number of nested timeouts before we start clamping. HTML says 5.
const uint32_t kClampTimeoutNestingLevel = 5u;
template <class T>
class AutoPtrComparator
{
@ -1956,7 +1959,10 @@ NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor, nsIInterfaceRequesto
struct WorkerPrivate::TimeoutInfo
{
TimeoutInfo()
: mId(0), mIsInterval(false), mCanceled(false)
: mId(0)
, mNestingLevel(0)
, mIsInterval(false)
, mCanceled(false)
{
MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
}
@ -1976,10 +1982,29 @@ struct WorkerPrivate::TimeoutInfo
return mTargetTime < aOther.mTargetTime;
}
void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
if (aBaseLevel < kClampTimeoutNestingLevel) {
mNestingLevel = aBaseLevel + 1;
return;
}
mNestingLevel = kClampTimeoutNestingLevel;
}
void CalculateTargetTime() {
auto target = mInterval;
if (mNestingLevel >= kClampTimeoutNestingLevel) {
target = TimeDuration::Max(
mInterval,
TimeDuration::FromMilliseconds(Preferences::GetInt("dom.min_timeout_value")));
}
mTargetTime = TimeStamp::Now() + target;
}
nsCOMPtr<nsIScriptTimeoutHandler> mHandler;
mozilla::TimeStamp mTargetTime;
mozilla::TimeDuration mInterval;
int32_t mId;
uint32_t mNestingLevel;
bool mIsInterval;
bool mCanceled;
};
@ -4130,6 +4155,7 @@ WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
, mMainThreadEventTarget(do_GetMainThread())
, mErrorHandlerRecursionCount(0)
, mNextTimeoutId(1)
, mCurrentTimerNestingLevel(0)
, mStatus(Pending)
, mFrozen(false)
, mTimerRunning(false)
@ -6076,6 +6102,7 @@ WorkerPrivate::SetTimeout(JSContext* aCx,
nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
newInfo->mIsInterval = aIsInterval;
newInfo->mId = timerId;
newInfo->AccumulateNestingLevel(this->mCurrentTimerNestingLevel);
if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
NS_WARNING("Timeout ids overflowed!");
@ -6188,14 +6215,21 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
// Guard against recursion.
mRunningExpiredTimeouts = true;
MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
// Run expired timeouts.
for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
TimeoutInfo*& info = expiredTimeouts[index];
AutoRestore<uint32_t> nestingLevel(this->mCurrentTimerNestingLevel);
if (info->mCanceled) {
continue;
}
// Set current timer nesting level to current running timer handler's
// nesting level
this->mCurrentTimerNestingLevel = info->mNestingLevel;
LOG(TimeoutsLog(), ("Worker %p executing timeout with original delay %f ms.\n",
this, info->mInterval.ToMilliseconds()));
@ -6273,8 +6307,10 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
info->mCanceled) {
if (info->mIsInterval && !info->mCanceled) {
// Reschedule intervals.
info->mTargetTime = info->mTargetTime + info->mInterval;
// Don't resort the list here, we'll do that at the end.
// Reschedule a timeout and, if needed, increase the nesting level.
info->AccumulateNestingLevel(info->mNestingLevel);
info->CalculateTargetTime();
// Don't re-sort the list here, we'll do that at the end.
++index;
}
else {

View file

@ -17,6 +17,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/CondVar.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/DOMEventTargetHelper.h"
@ -1016,6 +1017,20 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
uint32_t mErrorHandlerRecursionCount;
uint32_t mNextTimeoutId;
Status mStatus;
// Tracks the current setTimeout/setInterval nesting level.
// When there isn't a TimeoutHandler on the stack, this will be 0.
// Whenever setTimeout/setInterval are called, a new TimeoutInfo will be
// created with a nesting level one more than the current nesting level,
// saturating at the kClampTimeoutNestingLevel.
//
// When RunExpiredTimeouts is run, it sets this value to the
// TimeoutInfo::mNestingLevel for the duration of
// the WorkerScriptTimeoutHandler::Call which will explicitly trigger a
// microtask checkpoint so that any immediately-resolved promises will
// still see the nesting level.
uint32_t mCurrentTimerNestingLevel;
bool mFrozen;
bool mTimerRunning;
bool mRunningExpiredTimeouts;