performWorkUntilDeadline

performWorkUntilDeadline is the function that runs when the host callback fires. It starts one Scheduler time slice.

const performWorkUntilDeadline = () => {
  if (enableRequestPaint) {
    needsPaint = false;
  }
  if (isMessageLoopRunning) {
    const currentTime = getCurrentTime();
    startTime = currentTime;

    let hasMoreWork = true;
    try {
      hasMoreWork = flushWork(currentTime);
    } finally {
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
      }
    }
  }
};

This function sits between the host environment and Scheduler's real work loop.

Resetting needsPaint

The first branch resets needsPaint:

if (enableRequestPaint) {
  needsPaint = false;
}

needsPaint is used by the paint-yielding path. In this project, the main yielding behavior to focus on is still the time budget in shouldYieldToHost, but this reset keeps the paint flag scoped to a single Scheduler slice.

Guarding the Message Loop

The main body runs only when isMessageLoopRunning is true:

if (isMessageLoopRunning) {
  // perform work
}

That flag is set by requestHostCallback. If Scheduler has already stopped the message loop, a stale host callback should not do any work.

Capturing the Slice Start Time

Before running work, Scheduler captures the current time:

const currentTime = getCurrentTime();
startTime = currentTime;

There are two different startTime concepts in this codebase:

  • task.startTime: when a specific task becomes eligible to run.
  • module-level startTime: when the current Scheduler slice began.

shouldYieldToHost uses the module-level value to measure how long Scheduler has been blocking the main thread in this slice.

Running Work and Scheduling the Next Slice

The core call is:

hasMoreWork = flushWork(currentTime);

flushWork calls workLoop and returns true if there is more work to continue later.

The finally block decides what happens next:

finally {
  if (hasMoreWork) {
    schedulePerformWorkUntilDeadline();
  } else {
    isMessageLoopRunning = false;
  }
}

If more work remains, Scheduler posts another host callback. If no work remains, it marks the message loop as stopped.

The try/finally is deliberate. If a task throws, the error is not swallowed. The finally block still runs, and because hasMoreWork starts as true, Scheduler schedules another callback before the error propagates.

Summary

performWorkUntilDeadline owns one Scheduler slice. It records when the slice starts, calls flushWork, and either schedules another slice or shuts down the message loop.