Skip to content
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

7128 - React document listener memory leak fix. #7209

Closed

Conversation

mP1
Copy link

@mP1 mP1 commented Jul 7, 2016

This captures global (document) added event listeners, and saves them. When the last component is removed these "saved" listeners are also removed, and the object on document that keeps track of document state(mostly tracks which event listeners have already been added) is also "reset".

@ghost
Copy link

ghost commented Jul 7, 2016

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at [email protected]. Thanks!

@mP1
Copy link
Author

mP1 commented Jul 7, 2016

  1. Fork the repo and create your branch from master.

Done

  1. If you've added code that should be tested, add tests!

The big mem leak test - unfortunately cant be unit tested, needs a human to use something like Chrome dev tools -> timeline and profile to take snapshots, add and remove a react UI and verify memory stays constant.

Ive tried to add tests for the basic changes.

  1. If you've changed APIs, update the documentation.

Ive documented the added non private funcs.

Ensure the test suite passes (grunt test).

pass

Make sure your code lints (grunt lint) - we've done our best to make sure these rules match our internal linting guidelines.

My changes didnt introduce any new complaints. There are failures but they previously existed and are on files i have not touched.

  1. If you haven't already, complete the CLA.

Done.

@mP1
Copy link
Author

mP1 commented Jul 7, 2016

--My changes are very limited but i have gone thru and added some comments where i am unsure about style, supported browsers etc to help make things right.--

I am now using the map that tracks ids to components to know when the last component is removed.

if (remover && remover.remove) {
listenerRemovers.push(remover.remove);
}
};
Copy link
Author

@mP1 mP1 Jul 7, 2016

Choose a reason for hiding this comment

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

not sure if the names $trapBubbledEvent and $trapCapturedEvent should be different so theres no confusion about them being refs to the ones that live in ReactEventListener.

@mP1 mP1 force-pushed the 7128-unmount-last-component-document-reset branch from 89ce402 to 6c76f2f Compare July 7, 2016 12:30
@mP1
Copy link
Author

mP1 commented Jul 8, 2016

@syranide

I no longer use document.querySelectAll( the attribute that is used to mark react root nodes), and am instead using the map that tracks ids to components in ReactMount, to know when the last component was unmounted.

I added tests for new apis for ReactEventListener and the general mount/unmount cleanup the document (remove global event listeners).

@mP1 mP1 force-pushed the 7128-unmount-last-component-document-reset branch from 6c76f2f to 93d7c21 Compare July 8, 2016 05:02
@ghost ghost added the CLA Signed label Jul 12, 2016
@ghost
Copy link

ghost commented Jul 12, 2016

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@@ -328,6 +369,14 @@ var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, {
isListening[dependency] = true;
}
}

return function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Quickly reading this I'm having a little trouble understanding the exact flow, if I understand this correctly, this seems like a wasteful way of doing it. Allocating a new callback which holds an array of callbacks, instead of keeping a single global array of callbacks? Or am I missing something.

Copy link
Author

@mP1 mP1 Jul 13, 2016

Choose a reason for hiding this comment

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

@syranide

This function can accept documents and other types of targets. In both these cases it returns a remover that removes all registered listeners. To give a consistent interface it must therefore always return a remover for all types of target.

In this case, the caller knows if its adding to a document, and it does the right thing with the returned remover.

Its simply trying to match the style that whoever registers a listener also takes care of removing them at the right time.

Regarding the wastefulness of creating an array for each call, it hardly matters, as this isnt called that frequently, actually it only happens once if your "root" is the document, or once for the other "targets", which again wont be that many.

@syranide
Copy link
Contributor

Considering some listeners have side-effects, might it be a good idea to clean up listeners immediately whenever they are no longer used? (question for the team)

@ghost ghost added the CLA Signed label Jul 13, 2016
@mP1
Copy link
Author

mP1 commented Jul 17, 2016

NB:
The one failure in the build are lint errors in lines+files not included in this particular PR.

@mP1 mP1 force-pushed the 7128-unmount-last-component-document-reset branch 2 times, most recently from 49af1c4 to e9495c5 Compare July 20, 2016 05:47
@ghost ghost added the CLA Signed label Jul 20, 2016
@quantizor
Copy link
Contributor

Since React uses Jest for tests, you may actually be able to test for the presence of the global event listeners through a JSDOM escape hatch or test utility.

@gaearon
Copy link
Collaborator

gaearon commented Nov 21, 2017

Thanks for the PR! Sorry this didn't get proper attention.

My impression is that while it could be nice to fix, it's a somewhat uncommon request, and adds quite a bit of complexity.

I think the proper fix to this would be to do #2043 which we want eventually anyway. So I'll close this.

@gaearon gaearon closed this Nov 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants