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

fix: mutationobserver memory leak - use weakmap for bookkeeping #1423

Merged

Conversation

ravijayaramappa
Copy link
Contributor

Details

Use a WeakMap to store all nodes watched by an observer. On disconnect(), clean up references to the observer on all the nodes.

Limitation: If MutationObservers are not disconnected, this could lead to memory leak of nodes.

Does this PR introduce breaking changes?

  • No, it does not introduce breaking changes.

If yes, please describe the impact and migration path for existing applications.

The PR fulfills these requirements:

  • Have tests for the proposed changes been added? ✅
  • Have you followed these instructions to clearly describe the issue being fixed or feature enhanced? ✅

Use a WeakMap to store all nodes watched by an observer. On disconnect(), clean up references to the observer on all the nodes.

Limitation: If MutationObservers are not disconnected, this could lead to memory leak of nodes.
@salesforce-best-lwc-internal

This comment has been minimized.

// maintain a list of all nodes observed by this observer
if (observerToNodesMap.has(this)) {
const observedNodes = observerToNodesMap.get(this)!;
if (observedNodes.indexOf(target) === -1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

indexOf from lang

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

if (observerToNodesMap.has(this)) {
const observedNodes = observerToNodesMap.get(this)!;
if (observedNodes.indexOf(target) === -1) {
observedNodes.push(target);
Copy link
Contributor

Choose a reason for hiding this comment

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

push from lang.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

observedNodes.push(target);
}
} else {
observerToNodesMap.set(this, [target]);
Copy link
Contributor

Choose a reason for hiding this comment

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

set from lang, although we are not doing that today... we should probably start doing it at some point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its a polyfilled feature in IE11, wasn't sure if was okay to cache these. Let me know.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, it should be ok. they all run before everything else.

Copy link
Contributor

Choose a reason for hiding this comment

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

lets open a seprate issue for this in general.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

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

Looks great to me! :) I just had a minor nitpick.

if (observerToNodesMap.has(this)) {
const observedNodes = observerToNodesMap.get(this)!;
if (observedNodes.indexOf(target) === -1) {
observedNodes.push(target);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these be ArrayIndexOf.call() and ArrayPush.call?


// maintain a list of all nodes observed by this observer
if (observerToNodesMap.has(this)) {
const observedNodes = observerToNodesMap.get(this)!;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason we use ArrayPush.call() but not WeakMapHas.call()? I.e. we cache the Array prototype functions but not the WeakMap ones?

@salesforce-best-lwc-internal

This comment has been minimized.

@salesforce-best-lwc-internal

This comment has been minimized.

@salesforce-best-lwc-internal
Copy link

Benchmark results

Base commit: 7b6773b | Target commit: 7dac24c

lwc-engine-benchmark

table-append-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table/append/1k duration 139.60 (±4.80 ms) 140.55 (±3.80 ms) +1.0ms (0.7%) 👌
table-clear-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table/clear/1k duration 9.95 (±0.85 ms) 9.90 (±0.80 ms) -0.0ms (0.5%) 👌
table-create-10k metric base(7b6773b) target(7dac24c) trend
benchmark-table/create/10k duration 834.65 (±5.70 ms) 842.50 (±5.95 ms) +7.9ms (0.9%) 👎
table-create-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table/create/1k duration 106.25 (±2.70 ms) 107.40 (±3.45 ms) +1.2ms (1.1%) 👌
table-update-10th-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table/update-10th/1k duration 75.25 (±4.85 ms) 75.80 (±5.70 ms) +0.5ms (0.7%) 👌
tablecmp-append-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-component/append/1k duration 218.70 (±12.65 ms) 222.80 (±12.15 ms) +4.1ms (1.9%) 👌
tablecmp-clear-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-component/clear/1k duration 6.05 (±1.00 ms) 6.15 (±0.85 ms) +0.1ms (1.7%) 👌
tablecmp-create-10k metric base(7b6773b) target(7dac24c) trend
benchmark-table-component/create/10k duration 1626.30 (±21.35 ms) 1648.95 (±12.20 ms) +22.7ms (1.4%) 👎
tablecmp-create-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-component/create/1k duration 184.60 (±3.35 ms) 184.80 (±3.30 ms) +0.2ms (0.1%) 👌
tablecmp-update-10th-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-component/update-10th/1k duration 67.55 (±3.25 ms) 65.85 (±4.70 ms) -1.7ms (2.5%) 👌
wc-append-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-wc/append/1k duration 229.40 (±9.00 ms) 227.35 (±13.00 ms) -2.0ms (0.9%) 👌
wc-clear-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-wc/clear/1k duration 10.85 (±1.95 ms) 10.75 (±2.20 ms) -0.1ms (0.9%) 👌
wc-create-10k metric base(7b6773b) target(7dac24c) trend
benchmark-table-wc/create/10k duration 1835.65 (±13.35 ms) 1848.80 (±11.10 ms) +13.1ms (0.7%) 👎
wc-create-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-wc/create/1k duration 220.55 (±4.35 ms) 224.50 (±4.40 ms) +3.9ms (1.8%) 👎
wc-update-10th-1k metric base(7b6773b) target(7dac24c) trend
benchmark-table-wc/update-10th/1k duration 67.50 (±3.95 ms) 65.30 (±4.70 ms) -2.2ms (3.3%) 👌

@ravijayaramappa ravijayaramappa merged commit 165ad3b into master Jul 22, 2019
@ravijayaramappa ravijayaramappa deleted the ravi/master/mutation-observer-memoryleak-fix-weakmap branch July 22, 2019 19:47
ravijayaramappa added a commit that referenced this pull request Jul 22, 2019
* fix: mutationobserver memory leak - use weakmap for bookkeeping

Use a WeakMap to store all nodes watched by an observer. On disconnect(), clean up references to the observer on all the nodes.

Limitation: If MutationObservers are not disconnected, this could lead to memory leak of nodes.

* fix: comments fixes

* fix: address PR comments
@nolanlawson
Copy link
Collaborator

Limitation: If MutationObservers are not disconnected, this could lead to memory leak of nodes.

@ravijayaramappa Is this the case? I thought that if the MutationObserver (the key) has no more references, then both the key and the value (the array of nodes) should be removed from the WeakMap?

@ravijayaramappa
Copy link
Contributor Author

@nolanlawson Each observed node has a hard reference to the mutation observer[the observerLookupField property gives a list of observers]. This reference is never lost until the disconnect() is called. So there will be a reference to the Mutation Observer until its disconnect is called.

ravijayaramappa added a commit that referenced this pull request Jul 22, 2019
… (#1426)

* fix: mutationobserver memory leak - use weakmap for bookkeeping

Use a WeakMap to store all nodes watched by an observer. On disconnect(), clean up references to the observer on all the nodes.

Limitation: If MutationObservers are not disconnected, this could lead to memory leak of nodes.

* fix: comments fixes

* fix: address PR comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants