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

DRAFT: feat(memory-leak): add elements cleanup for did-intersect modifier #1078

Closed
wants to merge 1 commit into from

Conversation

BobrImperator
Copy link

Our client has been experiencing severe memory leaks. Which did-intersect was partially responsible for.
Currently I'm in the process of porting our monkey patches and documenting the issue.
We've also found ember-in-viewport to be causing these, which in turn is using intersection-observer-admin just like this addon.

Applying an identical fixes to both addons has fixed our problems.

With fix:
image

Without fix:
image

let originalRemoveElement = this.observerManagerService.removeElement;
const observerManagerService = this.observerManagerService;

observerManagerService.removeElement = function removeElementExt(
Copy link
Owner

Choose a reason for hiding this comment

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

I think this would be better accomplished with sinon.spy - it will still call the underlying function and then you can check the call count on the spy in your assertion.

@@ -8,6 +8,11 @@ export const DEFAULT_OBSERVER_OPTIONS = {};

function cleanup(instance) {
instance.unobserve.call(instance);
instance.observerManager.removeElement(instance.element);
instance.observerManager.removeElement(window);
Copy link
Owner

Choose a reason for hiding this comment

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

Why window cleanup here? This modifier doesn't explicitly subscribe to window at all.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I know it's weird. I'll be taking this to the interesction-observer-admin next.
I'd hope that this addon won't need these changes actually, since the issue lies on the dependency it uses.

But since I've had this experimental patch already, I decided I'd open a PR to showcase the issue. And frankly, I didn't think I'd get your attention so fast 😅

Copy link
Owner

Choose a reason for hiding this comment

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

Heh! Well, I may not have had a lot of time for active development, but I very much appreciate contributions and will do my best to be responsive (though I'm on vacation for a week starting Saturday, FYI). :)

I definitely think some fixes to the underlying dependency make sense in the context of this issue. If this is a workaround until you can get those fixes in, can you add a brief comment explaining why the workaround is needed, and then we can remove it once you land fixes upstream?

@@ -23,4 +23,9 @@ export default class ObserverManagerService extends Service {
willDestroy() {
this._admin.destroy();
}

removeElement(element) {
this._admin.elementRegistry.removeElement(element);
Copy link
Owner

Choose a reason for hiding this comment

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

elementRegistry is private. Are you sure we can access it?

https://github.com/snewcomer/intersection-observer-admin/blob/master/src/index.ts#L22


removeElement(element) {
this._admin.elementRegistry.removeElement(element);
this._admin.registry.removeElement(element);
Copy link
Owner

Choose a reason for hiding this comment

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

Where is registry defined on the intersection-observer-admin class?

Copy link
Owner

Choose a reason for hiding this comment

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

Do we perhaps need to make updates to the underlying class to expose a public method for this kind of cleanup?

Copy link
Author

Choose a reason for hiding this comment

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

We'll see, I'd imagine that that's what unobserve is for already, so hopefully it can hide all that

Copy link
Owner

Choose a reason for hiding this comment

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

Yea, it seems like maybe there's some extra steps missing from the unobserve implementation in intersection-observer-admin to release these references.


assert.strictEqual(
removedElementsCount,
49 * 2, // once for the element, once for a window object
Copy link
Owner

Choose a reason for hiding this comment

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

We should also assert against the underlying IntersectionObserverAdmin to ensure its registry gets cleaned up and prove that your implementation of removeElement works.

Copy link
Author

Choose a reason for hiding this comment

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

Initially I wanted to inspect the count of elements, but the registry is a WeakMap which doesn't have a length/count method.

Copy link
Owner

@elwayman02 elwayman02 Jun 25, 2024

Choose a reason for hiding this comment

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

I wonder if you could do something clever like grab a reference to one of the elements (which are the keys to the registry, I think?) and then assert that a get() call on the WeakMap returns undefined after it's been cleaned up? We wouldn't necessarily need to assert all of them, you could set up a separate test that just renders once and make sure that one gets cleaned up. 🤔

Copy link
Author

Choose a reason for hiding this comment

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

I did think about that but then the issue is that you'd probably want to make sure that you no longer hold this reference yourself, so it'd just get funky 👯

Copy link
Owner

Choose a reason for hiding this comment

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

What I'm thinking is that you grab the reference only in the test. The sinon spy for removeElement should be able to remember what its call arguments were, which you could use to get access to the element, and then you can assert against the registry contents from there.

@elwayman02
Copy link
Owner

Thanks for identifying this issue, I'd love to get it fixed. I left a few comments, and it looks like there are a few failing tests as well. Feel free to ping me once you have updates and I'll be happy to take another pass at it!

@elwayman02
Copy link
Owner

Does this fix in the underlying dependency help you at all in the context of this PR, if it lands?

snewcomer/intersection-observer-admin#49

@BobrImperator
Copy link
Author

That could be it, yeah

@elwayman02
Copy link
Owner

Alright, I'll see if I can poke Scott about getting that PR landed. If you need additional fixes, feel free to open a PR in that repo as well and I'll tell him to keep an eye out for it.

@BobrImperator BobrImperator changed the title feat(memory-leak): add elements cleanup for did-intersect modifier DRAFT: feat(memory-leak): add elements cleanup for did-intersect modifier Jun 27, 2024
@BobrImperator
Copy link
Author

FYI the current master of intersection-observer-admin appears to have fixed the issue with DidIntersect and it's no longer leaking.

For ember-in-viewport I had to apply an additional fix found on my fork here. Not everything there is needed and it also has changes to package.json to make it installable with a url.
Additionally it doesn't fix the issues entirely but it makes it a lot less severe.
I'll post more updates here once I find more issues and fixes.

@BobrImperator
Copy link
Author

Closing since intersection-observer-admin 0.3.4 has been released 🎉

https://github.com/snewcomer/intersection-observer-admin/releases/tag/v0.3.4-intersection-observer-admin

@elwayman02
Copy link
Owner

That's great! If you want to open a PR forcing that as the minimum version to bring in the bug fix, I'd be happy to have it. I know we're due for a new release, I've just been pretty busy, but at least you'll be unblocked on your end by just bumping the version in your own app.

@elwayman02
Copy link
Owner

@BobrImperator v7.2.0 contains the bumped version of intersection-observer-admin as a required dependency. Thanks again for bringing this up!

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.

2 participants