зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1672121
- Implement interruptible GC slice budgets r=jonco
Differential Revision: https://phabricator.services.mozilla.com/D109630
This commit is contained in:
Родитель
7eceb02f3d
Коммит
0181c4d9ce
|
@ -594,8 +594,7 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
|
|||
|
||||
if (aCCBeginTime.IsNull()) {
|
||||
// If no CC is in progress, use the standard slice time.
|
||||
return js::SliceBudget(js::TimeBudget(baseBudget),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
return js::SliceBudget(js::TimeBudget(baseBudget));
|
||||
}
|
||||
|
||||
// Only run a limited slice if we're within the max running time.
|
||||
|
@ -624,9 +623,8 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
|
|||
// Note: We may have already overshot the deadline, in which case
|
||||
// baseBudget will be negative and we will end up returning
|
||||
// laterSliceBudget.
|
||||
return js::SliceBudget(js::TimeBudget(std::max(
|
||||
{delaySliceBudget, laterSliceBudget, baseBudget})),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
return js::SliceBudget(js::TimeBudget(
|
||||
std::max({delaySliceBudget, laterSliceBudget, baseBudget})));
|
||||
}
|
||||
|
||||
TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
||||
|
|
|
@ -61,9 +61,6 @@ static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
|
|||
// Trigger a CC if the purple buffer exceeds this size when we check it.
|
||||
static const uint32_t kCCPurpleLimit = 200;
|
||||
|
||||
// How many cycle collected nodes to traverse between time checks.
|
||||
static const int64_t kNumCCNodesBetweenTimeChecks = 1000;
|
||||
|
||||
// Actions performed by the GCRunner state machine.
|
||||
enum class GCRunnerAction {
|
||||
WaitToMajorGC, // We want to start a new major GC
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "js/GCAnnotations.h"
|
||||
#include "js/shadow/Zone.h"
|
||||
#include "js/SliceBudget.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/UniquePtr.h"
|
||||
#include "js/Utility.h"
|
||||
|
@ -888,6 +889,20 @@ typedef void (*DoCycleCollectionCallback)(JSContext* cx);
|
|||
extern JS_PUBLIC_API DoCycleCollectionCallback
|
||||
SetDoCycleCollectionCallback(JSContext* cx, DoCycleCollectionCallback callback);
|
||||
|
||||
using CreateSliceBudgetCallback = js::SliceBudget (*)(JS::GCReason reason,
|
||||
int64_t millis);
|
||||
|
||||
/**
|
||||
* Called when generating a GC slice budget. It allows the embedding to control
|
||||
* the duration of slices and potentially check an interrupt flag as well. For
|
||||
* internally triggered GCs, the given millis parameter is the JS engine's
|
||||
* internal scheduling decision, which the embedding can choose to ignore.
|
||||
* (Otherwise, it will be the value that was passed to eg
|
||||
* JS::IncrementalGCSlice()).
|
||||
*/
|
||||
extern JS_PUBLIC_API void SetCreateGCSliceBudgetCallback(
|
||||
JSContext* cx, CreateSliceBudgetCallback cb);
|
||||
|
||||
/**
|
||||
* Incremental GC defaults to enabled, but may be disabled for testing or in
|
||||
* embeddings that have not yet implemented barriers on their native classes.
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define js_SliceBudget_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Variant.h"
|
||||
|
||||
|
@ -44,32 +45,56 @@ struct UnlimitedBudget {};
|
|||
* operations.
|
||||
*/
|
||||
class JS_PUBLIC_API SliceBudget {
|
||||
public:
|
||||
using InterruptRequestFlag = mozilla::Atomic<bool>;
|
||||
|
||||
private:
|
||||
static const intptr_t UnlimitedCounter = INTPTR_MAX;
|
||||
static const intptr_t DefaultStepsPerTimeCheck = 1000;
|
||||
|
||||
// Most calls to isOverBudget will only check the counter value. Every N
|
||||
// steps, do a more "expensive" check -- look at the current time and/or
|
||||
// check the atomic interrupt flag.
|
||||
static constexpr intptr_t StepsPerExpensiveCheck = 1000;
|
||||
|
||||
// Configuration
|
||||
|
||||
mozilla::Variant<TimeBudget, WorkBudget, UnlimitedBudget> budget;
|
||||
int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck;
|
||||
|
||||
int64_t counter;
|
||||
// External flag to request the current slice to be interrupted
|
||||
// (and return isOverBudget() early.) Applies only to time-based budgets.
|
||||
InterruptRequestFlag* interruptRequested = nullptr;
|
||||
|
||||
SliceBudget() : budget(UnlimitedBudget()), counter(UnlimitedCounter) {}
|
||||
// How many steps to count before checking the time and possibly the interrupt
|
||||
// flag.
|
||||
int64_t counter = StepsPerExpensiveCheck;
|
||||
|
||||
// This SliceBudget is considered interrupted from the time isOverBudget()
|
||||
// finds the interrupt flag set, to the next time resetOverBudget() (or
|
||||
// checkAndResetOverBudget()) is called.
|
||||
bool interrupted = false;
|
||||
|
||||
explicit SliceBudget(InterruptRequestFlag* irqPtr)
|
||||
: budget(UnlimitedBudget()),
|
||||
interruptRequested(irqPtr),
|
||||
counter(irqPtr ? StepsPerExpensiveCheck : UnlimitedCounter) {}
|
||||
|
||||
[[nodiscard]] bool isOverBudgetSlow();
|
||||
|
||||
public:
|
||||
// Use to create an unlimited budget.
|
||||
static SliceBudget unlimited() { return SliceBudget(); }
|
||||
static SliceBudget unlimited() { return SliceBudget(nullptr); }
|
||||
|
||||
// Instantiate as SliceBudget(TimeBudget(n)).
|
||||
explicit SliceBudget(TimeBudget time,
|
||||
int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck);
|
||||
InterruptRequestFlag* interrupt = nullptr);
|
||||
|
||||
explicit SliceBudget(mozilla::TimeDuration duration,
|
||||
InterruptRequestFlag* interrupt = nullptr)
|
||||
: SliceBudget(TimeBudget(duration.ToMilliseconds()), interrupt) {}
|
||||
|
||||
// Instantiate as SliceBudget(WorkBudget(n)).
|
||||
explicit SliceBudget(WorkBudget work);
|
||||
|
||||
explicit SliceBudget(mozilla::TimeDuration time)
|
||||
: SliceBudget(TimeBudget(time.ToMilliseconds())) {}
|
||||
|
||||
// Register having performed the given number of steps (counted against a
|
||||
// work budget, or progress towards the next time or callback check).
|
||||
void step(uint64_t steps = 1) {
|
||||
|
@ -91,8 +116,9 @@ class JS_PUBLIC_API SliceBudget {
|
|||
}
|
||||
|
||||
void resetOverBudget() {
|
||||
interrupted = false;
|
||||
if (isTimeBudget()) {
|
||||
counter = stepsPerTimeCheck;
|
||||
counter = StepsPerExpensiveCheck;
|
||||
} else if (isWorkBudget()) {
|
||||
counter = workBudget();
|
||||
}
|
||||
|
|
|
@ -375,6 +375,7 @@ GCRuntime::GCRuntime(JSRuntime* rt)
|
|||
helperThreadRatio(TuningDefaults::HelperThreadRatio),
|
||||
maxHelperThreads(TuningDefaults::MaxHelperThreads),
|
||||
helperThreadCount(1),
|
||||
createBudgetCallback(nullptr),
|
||||
rootsHash(256),
|
||||
nextCellUniqueId_(LargestTaggedNullCellPointer +
|
||||
1), // Ensure disjoint from null tagged pointers.
|
||||
|
@ -1402,16 +1403,21 @@ bool GCRuntime::isCompactingGCEnabled() const {
|
|||
rt->mainContextFromOwnThread()->compactingDisabledCount == 0;
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget(TimeBudget time, int64_t stepsPerTimeCheckArg)
|
||||
JS_PUBLIC_API void JS::SetCreateGCSliceBudgetCallback(
|
||||
JSContext* cx, JS::CreateSliceBudgetCallback cb) {
|
||||
cx->runtime()->gc.createBudgetCallback = cb;
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget(TimeBudget time, InterruptRequestFlag* interrupt)
|
||||
: budget(TimeBudget(time)),
|
||||
stepsPerTimeCheck(stepsPerTimeCheckArg),
|
||||
counter(stepsPerTimeCheckArg) {
|
||||
interruptRequested(interrupt),
|
||||
counter(StepsPerExpensiveCheck) {
|
||||
budget.as<TimeBudget>().deadline =
|
||||
ReallyNow() + TimeDuration::FromMilliseconds(timeBudget());
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget(WorkBudget work)
|
||||
: budget(work), counter(work.budget) {}
|
||||
: budget(work), interruptRequested(nullptr), counter(work.budget) {}
|
||||
|
||||
int SliceBudget::describe(char* buffer, size_t maxlen) const {
|
||||
if (isUnlimited()) {
|
||||
|
@ -1419,7 +1425,8 @@ int SliceBudget::describe(char* buffer, size_t maxlen) const {
|
|||
} else if (isWorkBudget()) {
|
||||
return snprintf(buffer, maxlen, "work(%" PRId64 ")", workBudget());
|
||||
} else {
|
||||
return snprintf(buffer, maxlen, "%" PRId64 "ms", timeBudget());
|
||||
return snprintf(buffer, maxlen, "%" PRId64 "ms%s", timeBudget(),
|
||||
interruptRequested ? ", interruptible" : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1431,6 +1438,15 @@ bool SliceBudget::isOverBudgetSlow() {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (interruptRequested && *interruptRequested) {
|
||||
*interruptRequested = false;
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
if (interrupted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReallyNow() >= budget.as<TimeBudget>().deadline) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3847,6 +3863,9 @@ void GCRuntime::collect(bool nonincrementalByAPI, const SliceBudget& budget,
|
|||
}
|
||||
|
||||
SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) {
|
||||
// millis == 0 means use internal GC scheduling logic to come up with
|
||||
// a duration for the slice budget. This may end up still being zero
|
||||
// based on preferences.
|
||||
if (millis == 0) {
|
||||
if (reason == JS::GCReason::ALLOC_TRIGGER) {
|
||||
millis = defaultSliceBudgetMS();
|
||||
|
@ -3857,6 +3876,13 @@ SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) {
|
|||
}
|
||||
}
|
||||
|
||||
// If the embedding has registered a callback for creating SliceBudgets,
|
||||
// then use it.
|
||||
if (createBudgetCallback) {
|
||||
return createBudgetCallback(reason, millis);
|
||||
}
|
||||
|
||||
// Otherwise, the preference can request an unlimited duration slice.
|
||||
if (millis == 0) {
|
||||
return SliceBudget::unlimited();
|
||||
}
|
||||
|
|
|
@ -921,6 +921,12 @@ class GCRuntime {
|
|||
// State used for managing atom mark bitmaps in each zone.
|
||||
AtomMarkingRuntime atomMarking;
|
||||
|
||||
/*
|
||||
* Pointer to a callback that, if set, will be used to create a
|
||||
* budget for internally-triggered GCs.
|
||||
*/
|
||||
MainThreadData<JS::CreateSliceBudgetCallback> createBudgetCallback;
|
||||
|
||||
private:
|
||||
// Arenas used for permanent things created at startup and shared by child
|
||||
// runtimes.
|
||||
|
|
|
@ -82,3 +82,58 @@ BEGIN_TEST(testSliceBudgetTimeZero) {
|
|||
return true;
|
||||
}
|
||||
END_TEST(testSliceBudgetTimeZero)
|
||||
|
||||
BEGIN_TEST(testSliceBudgetInterruptibleTime) {
|
||||
mozilla::Atomic<bool> wantInterrupt(false);
|
||||
|
||||
// Interruptible 100 second budget. This test will finish in well under that
|
||||
// time.
|
||||
static constexpr int64_t LONG_TIME = 100000;
|
||||
SliceBudget budget = SliceBudget(TimeBudget(LONG_TIME), &wantInterrupt);
|
||||
CHECK(!budget.isUnlimited());
|
||||
CHECK(!budget.isWorkBudget());
|
||||
CHECK(budget.isTimeBudget());
|
||||
|
||||
CHECK(budget.timeBudget() == LONG_TIME);
|
||||
|
||||
CHECK(!budget.isOverBudget());
|
||||
|
||||
// We do a little work, very small amount of time passes.
|
||||
budget.step(500);
|
||||
|
||||
// Not enough work to check interrupt, and no interrupt anyway.
|
||||
CHECK(!budget.isOverBudget());
|
||||
|
||||
// External signal: interrupt requested.
|
||||
wantInterrupt = true;
|
||||
|
||||
// Interrupt requested, but not enough work has been done to check for it.
|
||||
CHECK(!budget.isOverBudget());
|
||||
|
||||
// Do enough work for an expensive check.
|
||||
budget.step(1000);
|
||||
|
||||
// Interrupt requested! This will reset the external flag, but internally
|
||||
// remember that an interrupt was requested.
|
||||
CHECK(budget.isOverBudget());
|
||||
CHECK(!wantInterrupt);
|
||||
CHECK(budget.isOverBudget());
|
||||
|
||||
// Caller would handle the interrupt here.
|
||||
|
||||
budget.resetOverBudget();
|
||||
CHECK(!budget.isOverBudget());
|
||||
budget.step(5);
|
||||
CHECK(!budget.isOverBudget());
|
||||
|
||||
// The external signal gets picked up only when isOverBudget() is called.
|
||||
wantInterrupt = true;
|
||||
budget.step(2000);
|
||||
wantInterrupt = false;
|
||||
CHECK(!budget.isOverBudget());
|
||||
|
||||
// This doesn't test the deadline is correct as that would require waiting.
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testSliceBudgetInterruptibleTime)
|
||||
|
|
Загрузка…
Ссылка в новой задаче