Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/react-reconciler/src/ReactFiberWorkLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,11 +1114,29 @@ export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber): boolean {
return (executionContext & RenderContext) !== NoContext;
}

// When execution is interrupted by a breakpoint, alert(), or a browser
// debugging pause, the finally blocks that reset executionContext may not run.
Comment on lines +1117 to +1118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading comment about finally blocks

The comment says "the finally blocks that reset executionContext may not run," but renderRootSync (line 2736) and renderRootConcurrent (line 3019) restore executionContext via direct assignment outside any finally block. The RenderContext flag restoration is done linearly after the work loop, not in a finally. This means the stale context scenario for RenderContext isn't about finally blocks failing — it's about execution being interrupted before the cleanup line is reached.

Only the CommitContext restorations (e.g., lines 3870, 4034) actually use try/finally. Consider updating the comment to accurately describe the mechanism:

Suggested change
// When execution is interrupted by a breakpoint, alert(), or a browser
// debugging pause, the finally blocks that reset executionContext may not run.
// When execution is interrupted by a breakpoint, alert(), or a browser
// debugging pause, the code that restores executionContext after rendering
// (which is not in a finally block) may not have run yet. When React
// resumes, executionContext still has RenderContext or CommitContext
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-reconciler/src/ReactFiberWorkLoop.js
Line: 1117-1118

Comment:
**Misleading comment about `finally` blocks**

The comment says "the finally blocks that reset executionContext may not run," but `renderRootSync` (line 2736) and `renderRootConcurrent` (line 3019) restore `executionContext` via direct assignment **outside** any `finally` block. The `RenderContext` flag restoration is done linearly after the work loop, not in a `finally`. This means the stale context scenario for `RenderContext` isn't about `finally` blocks failing — it's about execution being interrupted before the cleanup line is reached.

Only the `CommitContext` restorations (e.g., lines 3870, 4034) actually use `try`/`finally`. Consider updating the comment to accurately describe the mechanism:

```suggestion
// When execution is interrupted by a breakpoint, alert(), or a browser
// debugging pause, the code that restores executionContext after rendering
// (which is not in a finally block) may not have run yet. When React
// resumes, executionContext still has RenderContext or CommitContext
```

How can I resolve this? If you propose a fix, please make it concise.

// When React resumes, executionContext still has RenderContext or CommitContext
// set even though we're not actually in the middle of work. Detect this by
// checking whether workInProgress/workInProgressRoot are null and clear the
// stale flags so we don't throw "Should not already be working."
function fixStaleExecutionContext(): void {
if (
(executionContext & (RenderContext | CommitContext)) !== NoContext &&
workInProgress === null &&
workInProgressRoot === null
) {
executionContext &= ~(RenderContext | CommitContext);
}
}
Comment on lines +1123 to +1131

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent state reset could mask real bugs

This function silently clears stale RenderContext/CommitContext flags without any indication that it activated. If there were ever a real bug in React's state management that left executionContext inconsistent (not caused by a debugger interruption), this fix would hide it.

Consider adding a __DEV__-mode warning when the stale context is detected and cleared, so that internal developers can distinguish between a genuine debugger interruption and an actual bug:

Suggested change
function fixStaleExecutionContext(): void {
if (
(executionContext & (RenderContext | CommitContext)) !== NoContext &&
workInProgress === null &&
workInProgressRoot === null
) {
executionContext &= ~(RenderContext | CommitContext);
}
}
function fixStaleExecutionContext(): void {
if (
(executionContext & (RenderContext | CommitContext)) !== NoContext &&
workInProgress === null &&
workInProgressRoot === null
) {
if (__DEV__) {
console.warn(
'Detected stale executionContext (%s) with no work in progress. ' +
'This is likely caused by a debugger breakpoint or alert() interrupting ' +
'a React render. Clearing stale flags.',
executionContext,
);
}
executionContext &= ~(RenderContext | CommitContext);
}
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-reconciler/src/ReactFiberWorkLoop.js
Line: 1123-1131

Comment:
**Silent state reset could mask real bugs**

This function silently clears stale `RenderContext`/`CommitContext` flags without any indication that it activated. If there were ever a real bug in React's state management that left `executionContext` inconsistent (not caused by a debugger interruption), this fix would hide it.

Consider adding a `__DEV__`-mode warning when the stale context is detected and cleared, so that internal developers can distinguish between a genuine debugger interruption and an actual bug:

```suggestion
function fixStaleExecutionContext(): void {
  if (
    (executionContext & (RenderContext | CommitContext)) !== NoContext &&
    workInProgress === null &&
    workInProgressRoot === null
  ) {
    if (__DEV__) {
      console.warn(
        'Detected stale executionContext (%s) with no work in progress. ' +
          'This is likely caused by a debugger breakpoint or alert() interrupting ' +
          'a React render. Clearing stale flags.',
        executionContext,
      );
    }
    executionContext &= ~(RenderContext | CommitContext);
  }
}
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.


export function performWorkOnRoot(
root: FiberRoot,
lanes: Lanes,
forceSync: boolean,
): void {
fixStaleExecutionContext();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix doesn't cover the flushPassiveEffectsImpl assertion

The stale context scenario can still crash before this fix runs. Both performConcurrentWorkOnRoot (ReactFiberRootScheduler.js:546) and performSyncWorkOnRoot (ReactFiberRootScheduler.js:611) call flushPendingEffects() before calling performWorkOnRoot(). That call chain reaches flushPassiveEffectsImpl() (line 4723), which has its own assertion:

if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
  throw new Error('Cannot flush passive effects while already rendering.');
}

If executionContext is stale and there are pending passive effects, this assertion fires before fixStaleExecutionContext() at line 1138 ever executes. Similarly, the fix at line 3532 in completeRoot is placed after the do { flushPendingEffects() } while (...) loop, so the same flushPassiveEffectsImpl assertion would fire first there too.

Consider also guarding the assertion in flushPassiveEffectsImpl (line 4723), or moving the fix to a shared entry point earlier in the call chain (e.g., inside flushPendingEffects itself).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-reconciler/src/ReactFiberWorkLoop.js
Line: 1138

Comment:
**Fix doesn't cover the `flushPassiveEffectsImpl` assertion**

The stale context scenario can still crash before this fix runs. Both `performConcurrentWorkOnRoot` ([ReactFiberRootScheduler.js:546](packages/react-reconciler/src/ReactFiberRootScheduler.js)) and `performSyncWorkOnRoot` ([ReactFiberRootScheduler.js:611](packages/react-reconciler/src/ReactFiberRootScheduler.js)) call `flushPendingEffects()` **before** calling `performWorkOnRoot()`. That call chain reaches `flushPassiveEffectsImpl()` (line 4723), which has its own assertion:

```javascript
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
  throw new Error('Cannot flush passive effects while already rendering.');
}
```

If `executionContext` is stale and there are pending passive effects, this assertion fires before `fixStaleExecutionContext()` at line 1138 ever executes. Similarly, the fix at line 3532 in `completeRoot` is placed **after** the `do { flushPendingEffects() } while (...)` loop, so the same `flushPassiveEffectsImpl` assertion would fire first there too.

Consider also guarding the assertion in `flushPassiveEffectsImpl` (line 4723), or moving the fix to a shared entry point earlier in the call chain (e.g., inside `flushPendingEffects` itself).

How can I resolve this? If you propose a fix, please make it concise.


if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
Expand Down Expand Up @@ -3511,6 +3529,8 @@ function completeRoot(
} while (pendingEffectsStatus !== NO_PENDING_EFFECTS);
flushRenderPhaseStrictModeWarningsInDEV();

fixStaleExecutionContext();

if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
Expand Down
Loading