Commit 0181c4d9 authored by Steve Fink's avatar Steve Fink
Browse files

Bug 1672121 - Implement interruptible GC slice budgets r=jonco

parent 7eceb02f
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -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,
+0 −3
Original line number Diff line number Diff line
@@ -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
+15 −0
Original line number Diff line number Diff line
@@ -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.
+36 −10
Original line number Diff line number Diff line
@@ -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();
    }
+31 −5
Original line number Diff line number Diff line
@@ -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();
  }
Loading