Skip to content

[Fizz] Abort tasks that suspend after aborting during render#36585

Open
gnoff wants to merge 4 commits into
facebook:mainfrom
gnoff:jstory/abort-suspended-after-abort
Open

[Fizz] Abort tasks that suspend after aborting during render#36585
gnoff wants to merge 4 commits into
facebook:mainfrom
gnoff:jstory/abort-suspended-after-abort

Conversation

@gnoff
Copy link
Copy Markdown
Collaborator

@gnoff gnoff commented May 31, 2026

Stacked on #36580

When a task calls abort() while it is rendering, Fizz intentionally leaves that task alone during the synchronous abort sweep so it can unwind normally. If the task then suspends before reaching a normal abort check, however, it currently remains pending and does not report the abort reason.

This change completes an aborted task once it has unwound back to the retry loop. Instead of treating it as an ordinary render error, it is routed through the existing abort task completion path so prerenders continue to postpone aborted work correctly and replay tasks use aborted resume semantics.

If the task suspended through use(), preserve its thenable state before completing the abort. This allows DEV async debug info to replay the suspended call site and include it in the owner stack, even though the task began aborting before it suspended.

Add coverage for render, prerender, and resumed replay tasks that suspend after initiating an abort, including a real-timer test verifying the suspended call site is retained in DEV owner stacks.

gnoff added 4 commits May 31, 2026 10:21
Previously Fizz represented an active abort using the `ABORTING` request status. This is ambiguous because aborting a task can synchronously fatal the request, transitioning it to `CLOSING` or `CLOSED` while another task is still unwinding from the same abort. Once that happened, the in-flight task no longer observed that the request was aborted and could fail to report its abort error.

This change removes the `ABORTING` status and instead tracks whether the request was aborted independently on the Request. The existing `fatalError` field continues to store the abort reason. As a result, tasks that were rendering when an abort occurred continue to observe the abort even if another aborted task has already fataled the request, allowing all relevant unfinished task errors to be reported. DEV stalled replays temporarily mask the aborted state so they can continue to reconstruct suspended call sites as before.

This also establishes explicit abort state on the Request for follow-up work that delays abort completion and allows rejected suspended work to provide more specific abort errors.
Fizz previously identified a task that was rendering during an abort by marking its blocked Segment as `RENDERING`. This does not work for resumed replay tasks because they do not have a Segment, so an abort during replay could eagerly abort the task before it unwound and then report the internal `null` throw instead of the abort reason.

Track the task currently executing on the Request so aborting can leave the in-flight task to unwind through its normal error path for both render and replay tasks. Since this replaces the only purpose of the Segment `RENDERING` status, remove that status and its associated bookkeeping.

When resumed work unwinds after aborting, use the request's abort reason in the replay catch paths so aborting while replaying a prerendered tree or while rendering a resumed segment reports the meaningful abort reason instead of the internal control-flow value.
`abort()` currently performs both the synchronous transition into an aborted request and the reporting/completion of every unfinished task in the same call. This change splits those phases. Aborting now synchronously marks the request as aborted, captures the abort reason, claims pending tasks so already scheduled work cannot continue rendering them, and captures any DEV async debug information needed at the point of abort. Reporting and completing the claimed tasks is then performed from a scheduled `finishAbort()` callback.

This split does not yet allow a promise rejected by an abort listener to replace the abort reason: work remains blocked once the request has been aborted, and tests assert that abort-time rejections still report the original abort reason. It establishes the task boundary needed for a follow-up change to selectively process rejected suspended work before completing the remaining aborted tasks.

This is observable for streaming renders because abort cleanup may now happen after already available output is read. A Suspense boundary that was previously converted to client rendering before it could be serialized may instead be emitted as pending first and receive its client-render instruction when the scheduled abort completion runs.

The scheduled finish must also preserve abort-during-render behavior in renderers whose scheduler executes synchronously. The request tracks its currently executing task, and both abort phases leave that task alone so it can unwind through its normal abort path rather than being completed twice or reporting an internal control-flow value.
When a task calls `abort()` while it is rendering, Fizz intentionally leaves that task alone during the synchronous abort sweep so it can unwind normally. If the task then suspends before reaching a normal abort check, however, it currently remains pending and does not report the abort reason.

This change completes an aborted task once it has unwound back to the retry loop. Instead of treating it as an ordinary render error, it is routed through the existing abort task completion path so prerenders continue to postpone aborted work correctly and replay tasks use aborted resume semantics.

If the task suspended through `use()`, preserve its thenable state before completing the abort. This allows DEV async debug info to replay the suspended call site and include it in the owner stack, even though the task began aborting before it suspended.

Add coverage for render, prerender, and resumed replay tasks that suspend after initiating an abort, including a real-timer test verifying the suspended call site is retained in DEV owner stacks.
@meta-cla meta-cla Bot added the CLA Signed label May 31, 2026
@gnoff gnoff requested review from eps1lon and unstubbable May 31, 2026 18:52
@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label May 31, 2026
@react-sizebot
Copy link
Copy Markdown

Comparing: 05ca66a...ff809d4

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB +0.05% 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 614.26 kB 614.26 kB = 108.57 kB 108.57 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 680.19 kB 680.19 kB = 119.51 kB 119.51 kB
facebook-www/ReactDOM-prod.classic.js = 700.61 kB 700.61 kB = 123.09 kB 123.09 kB
facebook-www/ReactDOM-prod.modern.js = 690.93 kB 690.93 kB = 121.48 kB 121.48 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server/cjs/react-server.development.js +1.33% 211.82 kB 214.63 kB +0.59% 37.43 kB 37.65 kB
oss-stable/react-server/cjs/react-server.development.js +1.33% 211.82 kB 214.63 kB +0.59% 37.43 kB 37.65 kB
oss-experimental/react-server/cjs/react-server.development.js +1.30% 216.61 kB 219.42 kB +0.62% 38.34 kB 38.58 kB
oss-experimental/react-server/cjs/react-server.production.js +1.15% 150.58 kB 152.31 kB +0.79% 26.68 kB 26.89 kB
oss-stable-semver/react-server/cjs/react-server.production.js +1.14% 147.09 kB 148.77 kB +0.82% 25.89 kB 26.10 kB
oss-stable/react-server/cjs/react-server.production.js +1.14% 147.09 kB 148.77 kB +0.82% 25.89 kB 26.10 kB
oss-experimental/react-markup/cjs/react-markup.development.js +0.68% 397.56 kB 400.27 kB +0.33% 72.16 kB 72.40 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +0.67% 410.51 kB 413.25 kB +0.36% 74.30 kB 74.57 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.67% 410.51 kB 413.25 kB +0.36% 74.30 kB 74.57 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +0.67% 410.54 kB 413.28 kB +0.36% 74.33 kB 74.59 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.67% 410.54 kB 413.28 kB +0.36% 74.33 kB 74.59 kB
facebook-www/ReactDOMServer-dev.modern.js +0.65% 421.05 kB 423.79 kB +0.34% 75.61 kB 75.86 kB
oss-experimental/react-markup/cjs/react-markup.production.js +0.65% 248.07 kB 249.68 kB +0.54% 46.00 kB 46.25 kB
facebook-www/ReactDOMServer-dev.classic.js +0.65% 424.49 kB 427.23 kB +0.34% 76.22 kB 76.48 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +0.64% 440.03 kB 442.87 kB +0.30% 78.47 kB 78.71 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +0.64% 440.11 kB 442.95 kB +0.30% 78.52 kB 78.76 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +0.64% 440.81 kB 443.65 kB +0.31% 78.66 kB 78.90 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +0.64% 440.89 kB 443.73 kB +0.30% 78.71 kB 78.95 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +0.64% 425.83 kB 428.57 kB +0.34% 76.53 kB 76.79 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.64% 425.83 kB 428.57 kB +0.34% 76.53 kB 76.79 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.64% 250.95 kB 252.56 kB +0.51% 45.22 kB 45.45 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.64% 250.97 kB 252.59 kB +0.51% 45.24 kB 45.47 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.64% 260.74 kB 262.40 kB +0.54% 46.64 kB 46.89 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +0.63% 447.07 kB 449.91 kB +0.30% 78.46 kB 78.69 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +0.63% 447.15 kB 449.98 kB +0.29% 78.51 kB 78.74 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +0.63% 425.27 kB 427.94 kB +0.30% 76.41 kB 76.64 kB
facebook-www/ReactDOMServer-prod.modern.js +0.63% 257.52 kB 259.14 kB +0.53% 46.00 kB 46.25 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +0.62% 456.39 kB 459.23 kB +0.33% 80.79 kB 81.05 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.62% 457.40 kB 460.24 kB +0.31% 81.03 kB 81.28 kB
facebook-www/ReactDOMServer-prod.classic.js +0.62% 259.86 kB 261.47 kB +0.52% 46.35 kB 46.59 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +0.62% 256.15 kB 257.74 kB +0.49% 47.20 kB 47.43 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +0.62% 256.18 kB 257.77 kB +0.49% 47.22 kB 47.45 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +0.62% 266.52 kB 268.16 kB +0.52% 48.86 kB 49.11 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.61% 463.16 kB 465.99 kB +0.31% 80.80 kB 81.05 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.60% 278.58 kB 280.27 kB +0.52% 49.56 kB 49.82 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.60% 278.66 kB 280.34 kB +0.52% 49.59 kB 49.84 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.60% 290.00 kB 291.73 kB +0.51% 51.19 kB 51.45 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +0.60% 279.62 kB 281.29 kB +0.50% 49.33 kB 49.57 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +0.59% 279.70 kB 281.36 kB +0.49% 49.35 kB 49.59 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.59% 290.88 kB 292.60 kB +0.48% 51.11 kB 51.35 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +0.59% 404.00 kB 406.38 kB +0.26% 76.27 kB 76.46 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +0.59% 389.78 kB 392.06 kB +0.30% 73.93 kB 74.15 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +0.59% 389.85 kB 392.13 kB +0.31% 73.96 kB 74.18 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +0.58% 284.49 kB 286.15 kB +0.45% 51.78 kB 52.01 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +0.58% 284.56 kB 286.23 kB +0.45% 51.80 kB 52.04 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.58% 296.58 kB 298.29 kB +0.48% 53.65 kB 53.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +0.57% 292.55 kB 294.21 kB +0.44% 51.62 kB 51.85 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +0.57% 292.63 kB 294.29 kB +0.45% 51.65 kB 51.88 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.56% 304.30 kB 306.02 kB +0.48% 53.50 kB 53.76 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +0.48% 267.75 kB 269.04 kB +0.39% 48.97 kB 49.17 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.44% 361.89 kB 363.50 kB +0.38% 67.32 kB 67.57 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +0.39% 685.99 kB 688.67 kB +0.23% 121.73 kB 122.01 kB

Generated by 🚫 dangerJS against ff809d4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants