Skip to content

Conversation

unstubbable
Copy link
Collaborator

@unstubbable unstubbable commented Aug 29, 2025

When the debug channel was already closed, we must not try to close it again when the Response gets garbage collected.

Test plan:

  1. reduce the Flight fixture App component to a minimum 1
    • remove everything from <body>
    • delete the console.log statement
  2. open the app in Firefox (seems to have a more aggressive GC strategy)
  3. wait a few seconds

On main, you will see the following error in the browser console:

TypeError: Can not close stream after closing or error

With this change, the error is gone.

Footnotes

  1. It's a bit concerning that step 1 is needed to reproduce the issue. Either GC is behaving differently with the unmodified App, or we may hold on to the Response under certain conditions, potentially creating a memory leak. This needs further investigation.

When the debug channel was already closed, we must not try to close it
again when the Response gets garbage collected.

Test plan:

1. reduce the Flight fixture App component to a minimum
    - remove everything from `<body>`
    - delete the `console.log` statement
2. open the app in Firefox (seems to have a more aggressive GC strategy)
3. wait a few seconds

On `main`, you will see the following error in the browser console:

```
TypeError: Can not close stream after closing or error
```

With this change, the error is gone.

It's a bit concerning that step 1 is needed to reproduce the issue.
Either GC is behaving differently with the unmodified App, or we may
hold on to the Response under certain conditions, potentially creating
a memory leak. This needs further investigation.
@meta-cla meta-cla bot added the CLA Signed label Aug 29, 2025
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Aug 29, 2025
@react-sizebot
Copy link

react-sizebot commented Aug 29, 2025

Comparing: 3fe51c9...a34a4c1

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.68 kB 6.68 kB +0.11% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.31 kB 530.31 kB = 93.39 kB 93.39 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 657.78 kB 657.78 kB = 115.66 kB 115.66 kB
facebook-www/ReactDOM-prod.classic.js = 677.76 kB 677.76 kB = 118.94 kB 118.94 kB
facebook-www/ReactDOM-prod.modern.js = 668.19 kB 668.19 kB = 117.26 kB 117.26 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against a34a4c1

@unstubbable unstubbable marked this pull request as ready for review August 29, 2025 12:44
@unstubbable unstubbable requested a review from eps1lon August 29, 2025 12:45
closeDebugChannel(debugChannel);
response._debugChannel = undefined;
// Make sure the debug channel is not closed a second time when the
// Response gets GC:ed.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

"GC:ed" is how we spell it in other places as well. On a related note: https://chatgpt.com/share/68b1a173-dfe8-8000-9114-bc44a40feaa8 😁

@unstubbable unstubbable marked this pull request as draft August 29, 2025 13:08
@unstubbable unstubbable removed the request for review from eps1lon August 29, 2025 13:08
@unstubbable unstubbable marked this pull request as ready for review August 29, 2025 13:20
@unstubbable unstubbable requested a review from eps1lon August 29, 2025 13:20
@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Aug 29, 2025

I believe that Fiber currently favors retaining owners from the original owner or stack vs updating to a new info from a new element. I think I originally meant this to optimize owner stacks to avoid re-reifying new stacks every time something rerenders.

However, this means that the debug information from those can retain the whole Response and keep it alive. Potentially forever as long as one instance is still mounted.

Another thing is that any Error that is not reified can hold to the stack frames as part of that stack until something access the .stack which is potentially forever. Those can then hold onto the Response through the call stack which keeps it alive.

Unless you keep adding one instance from every rerender it shouldn't be indefinitely though. Eventually you'll only have so many places that retain something that it eventually starts cleaning up but there can be many different Responses alive for a long time. Potentially the first one forever.

Two things we could do is 1) always override the debug stack/owner with the latest value on the Fiber 2) reify the .stack property eagerly (perhaps in idle cycles) on our own debug errors so that it can release callstacks. However that comes with other downsides.

@unstubbable unstubbable merged commit aad7c66 into facebook:main Aug 29, 2025
244 checks passed
@unstubbable unstubbable deleted the do-not-double-close-debug-channel branch August 29, 2025 15:22
github-actions bot pushed a commit to code/lib-react that referenced this pull request Aug 31, 2025
When the debug channel was already closed, we must not try to close it
again when the Response gets garbage collected.

**Test plan:**

1. reduce the Flight fixture `App` component to a minimum [^1]
    - remove everything from `<body>`
    - delete the `console.log` statement
2. open the app in Firefox (seems to have a more aggressive GC strategy)
3. wait a few seconds

On `main`, you will see the following error in the browser console:

```
TypeError: Can not close stream after closing or error
```

With this change, the error is gone.

[^1]: It's a bit concerning that step 1 is needed to reproduce the
issue. Either GC is behaving differently with the unmodified App, or we
may hold on to the Response under certain conditions, potentially
creating a memory leak. This needs further investigation.

DiffTrain build for [aad7c66](facebook@aad7c66)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Aug 31, 2025
When the debug channel was already closed, we must not try to close it
again when the Response gets garbage collected.

**Test plan:**

1. reduce the Flight fixture `App` component to a minimum [^1]
    - remove everything from `<body>`
    - delete the `console.log` statement
2. open the app in Firefox (seems to have a more aggressive GC strategy)
3. wait a few seconds

On `main`, you will see the following error in the browser console:

```
TypeError: Can not close stream after closing or error
```

With this change, the error is gone.

[^1]: It's a bit concerning that step 1 is needed to reproduce the
issue. Either GC is behaving differently with the unmodified App, or we
may hold on to the Response under certain conditions, potentially
creating a memory leak. This needs further investigation.

DiffTrain build for [aad7c66](facebook@aad7c66)
EugeneChoi4 pushed a commit to EugeneChoi4/react that referenced this pull request Sep 4, 2025
When the debug channel was already closed, we must not try to close it
again when the Response gets garbage collected.

**Test plan:**

1. reduce the Flight fixture `App` component to a minimum [^1]
    - remove everything from `<body>`
    - delete the `console.log` statement
2. open the app in Firefox (seems to have a more aggressive GC strategy)
3. wait a few seconds

On `main`, you will see the following error in the browser console:

```
TypeError: Can not close stream after closing or error
```

With this change, the error is gone.

[^1]: It's a bit concerning that step 1 is needed to reproduce the
issue. Either GC is behaving differently with the unmodified App, or we
may hold on to the Response under certain conditions, potentially
creating a memory leak. This needs further investigation.
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.

4 participants