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 memory leaks #328

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Conversation

iLiftALot
Copy link

@iLiftALot iLiftALot commented Jul 24, 2024

Memory Leak Error Message

This is a solution for the following error indicating potential memory leaks:

Error: Plugin "todoist-sync-plugin" is not passing Component in renderMarkdown.
This is needed to avoid memory leaks when embedded contents register global event handlers.
    at t.render (app.js:1:1298057)
    at t.renderMarkdown (app.js:1:1297838)
    at eval (plugin:todoist-sync-plugin:13:5673)
    at ev (plugin:todoist-sync-plugin:1:4888)
    at Array.map (<anonymous>)
    at eval (plugin:todoist-sync-plugin:4:3833)
    at sv (plugin:todoist-sync-plugin:4:1281)

I definitely notice a performance increase after the adjustment. This was happening because the component life cycle was not being properly managed. It was solved by creating a componentRef.

NOTE: It seems like this slightly changes the formatting of the final render, but nothing drastic. Personally, I enjoy the change, it appears more aesthetic in my opinion, so I opted to not make any changes.

Explanation of Changes Made

  1. Component Management

A componentRef is created to manage the lifecycle of the Component.
The componentRef is initialized if it's null, ensuring a single instance is used.

  1. Lifecycle Handling

The useEffect hook manages the rendering and cleanup of the component.
On cleanup (return () => { ... }), the renderChild is removed from the component, and the component is set to null to avoid memory leaks.

  1. Context Use

The RenderChildContext is used correctly to get the renderChild context.
The renderChild is added to the Component which is then passed to the MarkdownRenderer.renderMarkdown method.

@jamiebrynes7
Copy link
Owner

Hey @iLiftALot, thanks for the contribution. I'm a little bit confused though. In between the last public release (v1.13.0) and the current master, I've actually rewritten the UI rendering completely. This may go some way to explaining the aesthetic difference and performance change you observed.

The point though, is that I thought I did fix this issue during that rewrite. Did you observe the error on a build from master before your fix? Or have you only observed it when using the public release?

For completeness, in the previous releases, I was fetching the component ref from a Svelte context when rendering Markdown (https://github.com/jamiebrynes7/obsidian-todoist-plugin/blob/1.13.0/plugin/src/components/MarkdownRenderer.svelte#L13), but the context was never initialised properly leading to the error log you saw.

@iLiftALot
Copy link
Author

iLiftALot commented Jul 31, 2024

Hey @iLiftALot, thanks for the contribution. I'm a little bit confused though. In between the last public release (v1.13.0) and the current master, I've actually rewritten the UI rendering completely. This may go some way to explaining the aesthetic difference and performance change you observed.

The point though, is that I thought I did fix this issue during that rewrite. Did you observe the error on a build from master before your fix? Or have you only observed it when using the public release?

For completeness, in the previous releases, I was fetching the component ref from a Svelte context when rendering Markdown (https://github.com/jamiebrynes7/obsidian-todoist-plugin/blob/1.13.0/plugin/src/components/MarkdownRenderer.svelte#L13), but the context was never initialised properly leading to the error log you saw.

No problem @jamiebrynes7

I noticed the error in the developer console while using both the latest version of the public release (v1.13.0) as well as the latest master release.

Notice that a Component instance is created if it doesn't already exist. The renderChild context is then attached to the Component instance, which is passed within MarkdownRenderer.renderMarkdown, ensuring proper lifecycle management.

Additionally, I just submitted another update which adds a cleanup for any potential resources that are left over.

  • RIP

These additions solved the error on my end.

By no means am I an expert; I guarantee you are far ahead of me in terms of knowledge regarding the use of TypeScript/JavaScript, especially React. The purpose of this pull request is more to bring it to your attention, get your mind going on why this resolved the error, and potentially lead to an improved version.

I've studied Python for ~7-8 months before switching over to JavaScript/TypeScript over the past 2 months, so when I noticed the error, I looked at it as a great learning opportunity. Programming has evolved into an unhealthy obsession rather than a hobby 😂.

I also love the plugin and use it every day within my daily and weekly notes. Up until now I've never submitted a pull request.

I'm currently working on implementing additional features such as task deletion, pulling/displaying comments, etc. There are ways to implement these things according to the Todoist documentation, I just need to find my way around your plugin code 😅

My schedule is pretty open until the Fall, so let me know if you have more questions about this - I'd be glad to help in any way possible.

…n unmount. How about we test our commits before we push?? lmao
Copy link
Owner

@jamiebrynes7 jamiebrynes7 left a comment

Choose a reason for hiding this comment

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

Can you remove the .DS_Store files from the PR? And add it to the root .gitignore so they don't get added accidentally :)

Comment on lines +22 to +27
if (componentRef.current === null) {
componentRef.current = new Component();
}

// Attach renderChild to the component
componentRef.current.addChild(renderChild);
Copy link
Owner

@jamiebrynes7 jamiebrynes7 Aug 5, 2024

Choose a reason for hiding this comment

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

I'm still unsure that this is correct. renderChild is a reference to the Obsidian component wrapping the injected React component, so the relationship feels like it should be the other way around, i.e. - renderChild is the parent of the new component.

Looking at the docs for MarkdownRenderer.renderMarkdown:

     * @param component - A parent component to manage the lifecycle of the rendered child components.

Why doesn't renderChild satisfy this, it is a subclass of Component already - https://github.com/jamiebrynes7/obsidian-todoist-plugin/blob/master/plugin/src/query/injector.tsx#L47 (MarkdownRenderChild is a Component)?

Can you reproduce a situation whereby we can produce this error on the current state on master? I've been paying attention to the dev console since you've opened this PR and still haven't encountered it

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