From 3bbee255fdd4612c7c98c162f4c458289820b452 Mon Sep 17 00:00:00 2001 From: win7-7 Date: Wed, 28 Jan 2026 18:02:18 +0200 Subject: [PATCH] Revert "Compiles and does not crash." This reverts commit 48ba811bd6bed8b75d223eddf74c0785e7e81efd. --- dom/workers/RuntimeService.cpp | 14 + js/xpconnect/src/XPCJSContext.cpp | 32 ++ js/xpconnect/src/xpcprivate.h | 3 + xpcom/base/CycleCollectedJSContext.h | 21 ++ xpcom/base/SlowAsynchronousTaskScheduler.cpp | 371 +++++++++++++++++++ xpcom/base/SlowAsynchronousTaskScheduler.h | 157 ++++++++ 6 files changed, 598 insertions(+) create mode 100644 xpcom/base/SlowAsynchronousTaskScheduler.cpp create mode 100644 xpcom/base/SlowAsynchronousTaskScheduler.h diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index f63b1de981..93928db5d4 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -1102,6 +1102,20 @@ public: nsCycleCollector_doDeferredDeletion(); } + nsresult ScheduleTimerForThread(nsITimer* aTimer, + nsICancelableRunnable* aRunnable, + uint32_t aDelay) override + { + NS_ENSURE_STATE(mWorkerPrivate); + RefPtr wrapper = + new ExternalRunnableWrapper(mWorkerPrivate, aRunnable); + RefPtr target = + new TimerThreadEventTarget(mWorkerPrivate, wrapper); + aTimer->SetTarget(target); + return aTimer->InitWithFuncCallback(DummyCallback, nullptr, aDelay, + nsITimer::TYPE_ONE_SHOT); + } + virtual void CustomGCCallback(JSGCStatus aStatus) override { if (!mWorkerPrivate) { diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index 5e4479637e..481a420cd7 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -682,6 +682,38 @@ XPCJSContext::DispatchDeferredDeletion(bool aContinuation, bool aPurge) mAsyncSnowWhiteFreer->Dispatch(aContinuation, aPurge); } +class TimerCallbackForRunnable : public nsITimerCallback +{ +public: + TimerCallbackForRunnable(nsIRunnable* aRunnable) + : mRunnable(aRunnable) + {} + + NS_DECL_ISUPPORTS + NS_IMETHOD Notify(nsITimer* aTimer) final + { + mRunnable->Run(); + return NS_OK; + } + + nsCOMPtr mRunnable; +private: + virtual ~TimerCallbackForRunnable() {} +}; + +NS_IMPL_ISUPPORTS(TimerCallbackForRunnable, nsITimerCallback) + +nsresult +XPCJSContext::ScheduleTimerForThread(nsITimer* aTimer, + nsICancelableRunnable* aRunnable, + uint32_t aDelay) +{ + nsCOMPtr callback = + new TimerCallbackForRunnable(aRunnable); + aTimer->InitWithCallback(callback, aDelay, nsITimer::TYPE_ONE_SHOT); + return NS_OK; +} + void xpc_UnmarkSkippableJSHolders() { diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 381cec1646..0fd5761128 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -528,6 +528,9 @@ public: void BeginCycleCollectionCallback() override; void EndCycleCollectionCallback(mozilla::CycleCollectorResults& aResults) override; void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) override; + nsresult ScheduleTimerForThread(nsITimer* aTimer, + nsICancelableRunnable* aRunnable, + uint32_t aDelay) override; void CustomGCCallback(JSGCStatus status) override; void CustomOutOfMemoryCallback() override; diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index c0d7851568..281bd3ecf1 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -368,6 +368,27 @@ public: // isn't one. static CycleCollectedJSContext* Get(); + static SlowAsynchronousTaskScheduler* GetScheduler() + { + CycleCollectedJSContext* context = Get(); + return context ? &context->mSATS : nullptr; + } + + // Because of our Web Worker setup being broken, we need to specially initiate + // timers for Worker threads. + static nsresult ScheduleTimer(nsITimer* aTimer, + nsICancelableRunnable* aRunnable, + uint32_t aDelay) + { + CycleCollectedJSContext* context = Get(); + NS_ENSURE_STATE(context); + return context->ScheduleTimerForThread(aTimer, aRunnable, aDelay); + } + + virtual nsresult ScheduleTimerForThread(nsITimer* aTimer, + nsICancelableRunnable* aRunnable, + uint32_t aDelay) = 0; + // Add aZone to the set of zones waiting for a GC. void AddZoneWaitingForGC(JS::Zone* aZone) { diff --git a/xpcom/base/SlowAsynchronousTaskScheduler.cpp b/xpcom/base/SlowAsynchronousTaskScheduler.cpp new file mode 100644 index 0000000000..f330fc526c --- /dev/null +++ b/xpcom/base/SlowAsynchronousTaskScheduler.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "SlowAsynchronousTaskScheduler.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" + +//#define DEBUG_SATS 1 +using namespace mozilla; + +// If we're expecting a tick to happen soon, don't use timer based tick. +static const uint32_t kExpectedTimeOverlapLimit = 10; + +SlowAsynchronousTaskScheduler::SlowAsynchronousTaskScheduler() + +: mShuttingDown(false) +{ +} + +SlowAsynchronousTaskScheduler::~SlowAsynchronousTaskScheduler() +{ + MOZ_ASSERT(!mTimer); + while (ScheduledSlowTask* task = mScheduledSlowTasks.popFirst()) { + delete task; + } +} + +void +SlowAsynchronousTaskScheduler::ExpectedTick(uint32_t aMillisecondsFromNow) +{ + TimeStamp newExpectedTick = FromNow(aMillisecondsFromNow); + if (mExpectedNonTimerTick.IsNull() || + newExpectedTick < mExpectedNonTimerTick) { + mExpectedNonTimerTick = newExpectedTick; + } +} + +void +SlowAsynchronousTaskScheduler::Tick(bool aFromTimer) +{ + CancelTimer(); + if (mScheduledSlowTasks.isEmpty()) { + if (!aFromTimer) { + mExpectedNonTimerTick = TimeStamp(); + } + return; + } + TimeStamp now = TimeStamp::Now(); + TimeDuration limit = + TimeDuration::FromMilliseconds( + static_cast(kExpectedTimeOverlapLimit)); + + // We have a tick from timer, but we're possibly waiting for a non-timer + // tick happening real soon. If so, don't handle this tick. + + if (aFromTimer && !mExpectedNonTimerTick.IsNull() && + now < mExpectedNonTimerTick && + now + limit > mExpectedNonTimerTick) { + + // Try to run the timer right after the expected tick, since + // the expected tick might get lost. + uint32_t newTimerValue = + static_cast((mExpectedNonTimerTick - now).ToMilliseconds()) + 1; + EnsureTimer(newTimerValue); + return; + } + + mExpectedNonTimerTick = TimeStamp(); + ScheduledSlowTask* current = mScheduledSlowTasks.popFirst(); + + if (!current) { + return; + } + + if (current->mRunnable) { +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::Tick[timer=%s], runnable \n", + aFromTimer ? "true" : "false"); +#endif + nsCOMPtr runnable; + runnable.swap(current->mRunnable); + delete current; + current = nullptr; + runnable->Run(); + } else { +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::Tick[timer=%s] task=%p ", + aFromTimer ? "true" : "false", current->mSlowTasks); + for (uint32_t i = 0; current->mSlowTasks[i].mCallback; ++i) { + printf("[%u]", current->mSlowTasks[i].mID); + } +#endif + DependentSlowTask& task = + current->mSlowTasks[current->mIndexOfNextTask]; +#ifdef DEBUG_SATS + printf(", will run id=%u\n", task.mID); +#endif + // Run the currently scheduled task, and store state of the next task. + void* data = current->mDataForNextTask; + current->mDataForNextTask = nullptr; + + SATSState nextState = task.mCallback(task.mID, data); + uint32_t nextID = nextState.mState; + uint32_t i = 0; + for (; current->mSlowTasks[i].mCallback; ++i) { + if (current->mSlowTasks[i].mID == nextID) { + break; + } + } + + // The nextID did point to another task which has a callback to run. + // Schedule that. + if (current->mSlowTasks[i].mCallback) { + current->mIndexOfNextTask = i; + current->mDataForNextTask = nextState.mData; + // Reschedule, since it now has new mIndexOfNextTask. + AddScheduledSlowTask(current, current->mSlowTasks[i].mDelayMillis); + } else { + // Couldn't find anything to schedule. + delete current; + } + } + + // Make sure we have a timer running if we have something scheduled. + ScheduledSlowTask* first = mScheduledSlowTasks.getFirst(); + + if (first) { +#ifdef DEBUG_SATS + printf("Ensuring timer for the next task\n"); +#endif + uint32_t millis = first->mExpectedTimeToRun > now ? + static_cast( + (first->mExpectedTimeToRun - now).ToMilliseconds()) : + 0; + EnsureTimer(millis); + } +} + +void +SlowAsynchronousTaskScheduler::Schedule(nsIRunnable* aRunnable, + uint32_t aMillis) +{ +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::Schedule aRunnable\n"); +#endif + + AddScheduledSlowTask(new ScheduledSlowTask(aRunnable), aMillis); + EnsureTimer(aMillis); +} + +void +SlowAsynchronousTaskScheduler::Schedule(DependentSlowTask* aTasks, + void* aData) + +{ +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::Schedule aTasks "); + for (uint32_t i = 0; aTasks[i].mCallback; ++i) { + printf("[%u]", aTasks[i].mID); + } + printf("\n"); +#endif + + MOZ_ASSERT(aTasks && aTasks[0].mCallback); + AddScheduledSlowTask(new ScheduledSlowTask(aTasks, 0, aData), + aTasks[0].mDelayMillis); + EnsureTimer(aTasks[0].mDelayMillis); +} + +void +SlowAsynchronousTaskScheduler::Schedule(DependentSlowTask* aTasks, + uint32_t aID, void* aData) +{ +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::Schedule aTask "); + for (uint32_t i = 0; aTasks[i].mCallback; ++i) { + printf("[%u]", aTasks[i].mID); + } + printf(", scheduling=%u\n", aID); +#endif + + for (uint32_t i = 0; aTasks[i].mCallback; ++i) { + if (aTasks[i].mID == aID) { + MOZ_ASSERT(aTasks[i].mCallback); + AddScheduledSlowTask(new ScheduledSlowTask(aTasks, i, aData), + aTasks[i].mDelayMillis); + EnsureTimer(aTasks[i].mDelayMillis); + return; + } + } + MOZ_ASSERT(false, "Unknown ID?"); +} + +void +SlowAsynchronousTaskScheduler::CancelScheduledTask(nsIRunnable* aRunnable) +{ + if (aRunnable) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + ScheduledSlowTask* next = task->getNext(); + if (task->mRunnable == aRunnable) { + task->remove(); + delete task; + } + task = next; + } + } +} + +void +SlowAsynchronousTaskScheduler::CancelScheduledTask(DependentSlowTask* aTasks) +{ + if (aTasks) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + ScheduledSlowTask* next = task->getNext(); + if (task->mSlowTasks == aTasks) { + task->remove(); + delete task; + } + task = next; + } + } +} + +void +SlowAsynchronousTaskScheduler::CancelScheduledTask(DependentSlowTask* aTasks, + uint32_t aID) +{ + if (aTasks) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + ScheduledSlowTask* next = task->getNext(); + if (task->mSlowTasks == aTasks && + task->mSlowTasks[task->mIndexOfNextTask].mID == aID) { + task->remove(); + delete task; + } + task = next; + } + } +} + +bool +SlowAsynchronousTaskScheduler::IsScheduled(nsIRunnable* aRunnable) +{ + if (aRunnable) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + if (task->mRunnable == aRunnable) { + return true; + } + task = task->getNext(); + } + } + return false; +} + +bool +SlowAsynchronousTaskScheduler::IsScheduled(DependentSlowTask* aTasks) +{ + if (aTasks) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + if (task->mSlowTasks == aTasks) { + return true; + } + task = task->getNext(); + } + } + return false; +} + +bool +SlowAsynchronousTaskScheduler::IsScheduled(DependentSlowTask* aTasks, + uint32_t aID) +{ + if (aTasks) { + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + if (task->mSlowTasks == aTasks && + task->mSlowTasks[task->mIndexOfNextTask].mID == aID) { + return true; + } + task = task->getNext(); + } + } + return false; +} + +class SATSTimerTick : public CancelableRunnable +{ +public: + NS_IMETHOD Run() + { + if (!mCancelled) { + SlowAsynchronousTaskScheduler* sats = + CycleCollectedJSContext::GetScheduler(); + if (sats) { + sats->TickFromTimer(); + } + } + return NS_OK; + } + NS_IMETHOD Cancel() + { + mCancelled = true; + return NS_OK; + } + bool mCancelled = false; +}; + +void +SlowAsynchronousTaskScheduler::EnsureTimer(uint32_t aMillis) +{ +#ifdef DEBUG_SATS + printf("SlowAsynchronousTaskScheduler::EnsureTimer %ums\n", aMillis); +#endif + TimeStamp fromNow = FromNow(aMillis); + if (mTimer) { + if (!mTimerTime.IsNull() && mTimerTime < fromNow) { + // We will run the timer sooner than aMillis from now. + return; + } + mTimer->Cancel(); + } else { + if (mShuttingDown) { + return; + } + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mTimer) { + NS_WARNING("No timer!"); + return; + } + mTimerCallback = new SATSTimerTick(); + } + mTimerTime = fromNow; + CycleCollectedJSContext::ScheduleTimer(mTimer, mTimerCallback, aMillis); +} + +void +SlowAsynchronousTaskScheduler::AddScheduledSlowTask(ScheduledSlowTask* aTask, + uint32_t aMillis) +{ + TimeStamp expectedTime = FromNow(aMillis); + ScheduledSlowTask* task = mScheduledSlowTasks.getFirst(); + while(task) { + if (task->mExpectedTimeToRun > expectedTime) { + task->setPrevious(aTask); + break; + + } + task = task->getNext(); + + } + if (!task) { + mScheduledSlowTasks.insertBack(aTask); + } + aTask->mExpectedTimeToRun = expectedTime; +} + +void +SlowAsynchronousTaskScheduler::CancelTimer() +{ + if (mTimer) { + mTimer->Cancel(); + mTimerTime = TimeStamp(); + } +} \ No newline at end of file diff --git a/xpcom/base/SlowAsynchronousTaskScheduler.h b/xpcom/base/SlowAsynchronousTaskScheduler.h new file mode 100644 index 0000000000..5270e07802 --- /dev/null +++ b/xpcom/base/SlowAsynchronousTaskScheduler.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 mozilla_SlowAsynchronousTaskScheduler_h__ +#define mozilla_SlowAsynchronousTaskScheduler_h__ + +#include "nsThreadUtils.h" +#include "nsITimer.h" +#include "mozilla/LinkedList.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { + +struct SATSState +{ + MOZ_IMPLICIT SATSState(uint32_t aState) + : mState(aState), mData(nullptr) + {} + + MOZ_IMPLICIT SATSState(uint32_t aState, void* aData) + : mState(aState), mData(aData) + {} + + MOZ_IMPLICIT SATSState(const SATSState& aOther) + : mState(aOther.mState), mData(aOther.mData) + {} + + uint32_t mState; + void* mData; + +private: + SATSState() = delete; +}; + +// Returns the ID of the next DependentSlowTask. +typedef SATSState (*SlowTaskCallback)(uint32_t aCurrentID, void* aData); + +struct DependentSlowTask +{ + uint32_t mID; + uint32_t mDelayMillis; + SlowTaskCallback mCallback; +}; + +// If one has several tasks which need to run occasionally and those tasks +// depend on the each others, one can pass pointer to an array of +// DependentSlowTasks objects. Each callback returns the ID of the next state. +// +// enum { +// eIDOfState1, +// eIdOfState2, +// eIdOfFinishState +// }; +// +// static DependentSlowTask sMySlowTasks[] +// { +// { eIDOfState1, 10000, TriggerState1 }, +// { eIdOfState2, 5000, TriggerState2 }, +// { eIdOfFinishState, 0, nullptr } +// }; +// +// scheduler->Schedule(sMySlowTasks); +class SlowAsynchronousTaskScheduler +{ +public: + SlowAsynchronousTaskScheduler(); + ~SlowAsynchronousTaskScheduler(); + + // Call ExpectedTick() to give SATS hint when the next non-timer based Tick + // might be called. For example refresh driver could call this. + void ExpectedTick(uint32_t aMillisecondsFromNow); + + // Call Tick() to run possible pending scheduled tasks. + void Tick() + + { + Tick(false); + } + + void TickFromTimer() + { + Tick(true); + } + + void Schedule(nsIRunnable* aRunnable, uint32_t aMillis); + void Schedule(DependentSlowTask* aTasks, void* aData = nullptr); + void Schedule(DependentSlowTask* aTasks, uint32_t aID, void* aData = nullptr); + + void CancelScheduledTask(nsIRunnable* aRunnable); + void CancelScheduledTask(DependentSlowTask* aTasks); + void CancelScheduledTask(DependentSlowTask* aTasks, uint32_t aID); + + bool IsScheduled(nsIRunnable* aRunnable); + bool IsScheduled(DependentSlowTask* aTasks); + bool IsScheduled(DependentSlowTask* aTasks, uint32_t aID); + void Shutdown() + { + CancelTimer(); + mTimer = nullptr; + mShuttingDown = true; + } +private: + class ScheduledSlowTask : public LinkedListElement + { + public: + ScheduledSlowTask(DependentSlowTask* aSlowTasks, uint32_t aIndex = 0, + void* aData = nullptr) + : mSlowTasks(aSlowTasks) + , mIndexOfNextTask(aIndex) + , mDataForNextTask(aData) + { + MOZ_COUNT_CTOR(ScheduledSlowTask); + } + + ScheduledSlowTask(nsIRunnable* aSlowTask) + : mRunnable(aSlowTask) + , mSlowTasks(nullptr) + , mIndexOfNextTask(0) + , mDataForNextTask(nullptr) + { + MOZ_COUNT_CTOR(ScheduledSlowTask); + } + + ~ScheduledSlowTask() + { + MOZ_COUNT_DTOR(ScheduledSlowTask); + } + + mozilla::TimeStamp mExpectedTimeToRun; + nsCOMPtr mRunnable; + DependentSlowTask* mSlowTasks; + uint32_t mIndexOfNextTask; + void* mDataForNextTask; + }; + + void Tick(bool aFromTimer); + void AddScheduledSlowTask(ScheduledSlowTask* aTask, uint32_t aMillis); + void EnsureTimer(uint32_t aMillis); + void CancelTimer(); + static TimeStamp FromNow(uint32_t aMillis) + { + return TimeStamp::Now() + + TimeDuration::FromMilliseconds(static_cast(aMillis)); + } + + LinkedList mScheduledSlowTasks; + nsCOMPtr mTimer; + mozilla::TimeStamp mTimerTime; + nsCOMPtr mTimerCallback; + mozilla::TimeStamp mExpectedNonTimerTick; + + bool mShuttingDown; +}; +} +#endif \ No newline at end of file