From 51277f511c9a4302ca1f28dba50c1398edcdbb8d Mon Sep 17 00:00:00 2001 From: win7-7 Date: Thu, 22 Jan 2026 21:57:23 +0200 Subject: [PATCH] Implement a callback system for interrupting timed slice budgets for bug #1351790 Trying this again. --- js/public/SliceBudget.h | 41 +++++++++++++++++++++++++++++--- js/src/jsgc.cpp | 52 ++++++++++++++++++++--------------------- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/js/public/SliceBudget.h b/js/public/SliceBudget.h index e170ea2e42..f818957123 100644 --- a/js/public/SliceBudget.h +++ b/js/public/SliceBudget.h @@ -34,12 +34,18 @@ struct JS_PUBLIC_API(WorkBudget) */ class JS_PUBLIC_API(SliceBudget) { + typedef bool (*InterruptRequestCallback)(void*); + static const int64_t unlimitedDeadline = INT64_MAX; static const intptr_t unlimitedStartCounter = INTPTR_MAX; bool checkOverBudget(); - SliceBudget(); + SliceBudget() + : timeBudget(UnlimitedTimeBudget), workBudget(UnlimitedWorkBudget), callbackBudget(nullptr) + { + reset(); + } public: // Memory of the originally requested budget. If isUnlimited, neither of @@ -56,22 +62,51 @@ class JS_PUBLIC_API(SliceBudget) static const int64_t UnlimitedTimeBudget = -1; static const int64_t UnlimitedWorkBudget = -1; + SliceBudget* callbackBudget; + // If callbackBudget is nullptr, the following fields are uninitialized and unused. + InterruptRequestCallback interruptRequested; + void* callbackData; + /* Use to create an unlimited budget. */ static SliceBudget unlimited() { return SliceBudget(); } + explicit SliceBudget(TimeBudget time, + SliceBudget& callbackBudget_, InterruptRequestCallback callback, + void* callbackData) + : timeBudget(time), workBudget(UnlimitedWorkBudget) + , callbackBudget(&callbackBudget_), interruptRequested(callback) + , callbackData(callbackData) + { + reset(); + } + /* Instantiate as SliceBudget(TimeBudget(n)). */ - explicit SliceBudget(TimeBudget time); + explicit SliceBudget(TimeBudget time) + : timeBudget(time), workBudget(UnlimitedWorkBudget), callbackBudget(nullptr) + { + reset(); + } /* Instantiate as SliceBudget(WorkBudget(n)). */ - explicit SliceBudget(WorkBudget work); + explicit SliceBudget(WorkBudget work) + : timeBudget(UnlimitedTimeBudget), workBudget(work), callbackBudget(nullptr) + { + reset(); + } + + void reset(); void makeUnlimited() { deadline = unlimitedDeadline; counter = unlimitedStartCounter; + if (callbackBudget) + callbackBudget->makeUnlimited(); } void step(intptr_t amt = 1) { counter -= amt; + if (callbackBudget) + callbackBudget->step(amt); } bool isOverBudget() { diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 2a49b682d0..3822e0a348 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2938,33 +2938,23 @@ ArenaLists::queueForegroundThingsForSweep(FreeOp* fop) gcScriptArenasToUpdate = arenaListsToSweep[AllocKind::SCRIPT]; } -SliceBudget::SliceBudget() - : timeBudget(UnlimitedTimeBudget), workBudget(UnlimitedWorkBudget) +void +SliceBudget::reset() { - makeUnlimited(); -} + MOZ_ASSERT_IF(timeBudget.budget < 0, !callbackBudget); + if (timeBudget.budget >= 0) { -SliceBudget::SliceBudget(TimeBudget time) - : timeBudget(time), workBudget(UnlimitedWorkBudget) -{ - if (time.budget < 0) { - makeUnlimited(); - } else { // Note: TimeBudget(0) is equivalent to WorkBudget(CounterReset). - deadline = PRMJ_Now() + time.budget * PRMJ_USEC_PER_MSEC; + deadline = PRMJ_Now() + timeBudget.budget * PRMJ_USEC_PER_MSEC; counter = CounterReset; - } -} - -SliceBudget::SliceBudget(WorkBudget work) - : timeBudget(UnlimitedTimeBudget), workBudget(work) -{ - if (work.budget < 0) { - makeUnlimited(); - } else { - deadline = 0; - counter = work.budget; - } + if (callbackBudget) + callbackBudget->reset(); + } else if (workBudget.budget >= 0) { + deadline = 0; + counter = workBudget.budget; + } else { + makeUnlimited(); + } } int @@ -2981,10 +2971,20 @@ SliceBudget::describe(char* buffer, size_t maxlen) const bool SliceBudget::checkOverBudget() { - bool over = PRMJ_Now() >= deadline; - if (!over) + int64_t now = PRMJ_Now(); + bool over = now >= deadline; + if (over) + return true; + if (!callbackBudget || !callbackBudget->isOverBudget()) { counter = CounterReset; - return over; + return false; + } + callbackBudget->reset(); + if (interruptRequested(callbackData)) { + deadline = now; + return true; + } + return false; } void