-
Notifications
You must be signed in to change notification settings - Fork 47.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Password field causes memory leak in production builds #12420
Comments
Interesting! I wonder what's going on. Do you know if this holds true for Firefox or another browser? To my knowledge, password inputs do not receive any special treatment: https://github.com/facebook/react/search?utf8=%E2%9C%93&q=password&type= |
@nhunzaker unfortunately i did not fid a way to check for detached dom nodes in firefox or safari 😞 |
Have you tried a similar test without React? |
Here is a similar vanilla example. I can't see any detached element in the memory snapshot: <!DOCTYPE html>
<html>
<body>
<button id="add10">Add 10 input</button>
<button id="add1000">Add 1000 input</button>
<button id="add10000">Add 10000 input</button>
<button id="clear">Clear</button>
<div id="content"></div>
<script>
const content = document.getElementById('content');
const add = n => () => {
console.log('click');
for(let i = 0; i < n; i++){
const input = document.createElement('input');
input.setAttribute('type', 'password');
content.appendChild(input);
}
}
document.getElementById('add10').addEventListener('click', add(10));
document.getElementById('add1000').addEventListener('click', add(1000));
document.getElementById('add10000').addEventListener('click', add(10000));
document.getElementById('clear').addEventListener('click', () => {
content.innerHTML = '';
});
</script>
</body>
</html> |
I've tried debugging this a little bit, and I don't see any problems with password inputs specifically. Using the example posted, there are a few detached nodes that are referenced from the Fiber instance. For example, the Fiber for the I don't see any behavior that would indicate a memory leak though. Taking a profile and performing GC, it looks like the detached nodes eventually get cleaned up. But it's possible it could be more efficient about storing references to detached elements. |
My understanding is that we maintain a reference to detached tree for one more update after it's been removed. On the next update the old node should get cleared. Does that help? react/packages/react-reconciler/src/ReactFiberCommitWork.js Lines 494 to 498 in 6b99c6f
|
Thanks @gaearon. @dcodus can you verify if this is what you're seeing? A few detached nodes is expected, but they should be GC'd. Can you verify whether there's actually a memory leak by profiling memory over time in the Performance tab? |
That's not quite accurate. What I'm saying is that the nodes that were unmounted during previous update won't be GC'd until the next update. So as user interacts with the app, eventually all detached nodes get GC'd but it happens on next update rather than on the update that caused them to unmount. |
Right, that's what I'm saying. I should have been clearer: they should be GC'd eventually, assuming an update is processed. Meaning that there shouldn't be a memory leak, since there should be some constant number of detached nodes that get GC'd as the application updates. |
I decided to take a crack at this. From what I can tell, React is doing exactly what we'd expect it to do. I setup a scenario in which the the memory leak should be very pronounced if it exists:
Letting this run in Chrome for a bit, I watched the Performance tab for allocations and garbage collections. Things there look about like you'd expect for an eventual garbage collection. And These each represent about 60 seconds of self-toggling every 18ms. You can see the GC eventually catching up and reaping the detached nodes. I ran this a few times with the same result. Also, it didn't seem to matter whether it was a text input or a password. |
Seems like there's no leak then. |
Calling setState in render to avoid this smells really bad. Don't we have a way to tell react to GC the removed child without using 2-step render? @gaearon |
I don’t think anyone suggests actually calling setState in render 🙂 I don’t understand the problem you’re having, @alivedise. If it gets cleared on next update what difference does it make to you? Can you explain a realistic scenario where this matters? |
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
We're noticing that password-type input fields (as well as their wrapping parents) remain as detached DOM elements when conditionally being rendered and removed from the DOM.
This issue seems to be occurring across several applications using React 16, include a newly created app from
Create React App
, and we're able to produce Chrome memory heap snapshots in all of these environments to demonstrate this issue.Observe in the following example, the button adds/removes the input field to the DOM. To reproduce the problem stated in this issue, repeatedly click this button (say 20-30 times), then take a memory heap snapshot. You will see multiple detached DOM elements, the password
input
field and its wrapping div. Note that this issue does not happen for other types of input fields.Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
We have tried this issue in React 16.2, 16.1 and React 15.6. Chrome and Canary were the browsers tested in particular.
The text was updated successfully, but these errors were encountered: