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 for: memory leaks #2969

Merged
merged 25 commits into from
Mar 26, 2019
Merged

Fix for: memory leaks #2969

merged 25 commits into from
Mar 26, 2019

Conversation

engineering-this
Copy link
Contributor

What is the purpose of this pull request?

Bug fix

Does your PR contain necessary tests?

All patches which change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.

This PR contains

  • Unit tests
  • Manual tests

What changes did you make?

List of found and fixed memory leaks/ potential memory leaks:

Magicline

Magicline plugin exposes backdoor object property for debugging purposes in CKEDITOR.plugins.magicline, this object is replaced with each editor creation.
To fix memory leaks I've moved this property into editor._.magicLineBackdoor, so all references are removed along with editor instance.

Widget styles

Widget plugin keeps registered styles in styleGroups local variable. With every editor creation new styles are added. I've added check that prevents adding same styles again. At first I've used styleDefinition object for comparisement, but easyimage creates them dynamically, so I've changed solution to deeply compare object, to make sure only new styles are stored.

Script onload and onerror

CKEDITOR.scriptLoader and CKEDITOR.loader weren't removing onload/onerror listeners once loading is finished. Listeners keeps references to things that otherwise might be cleaned by Garbage Collector.

Image2 dialog

Image2 plugin adds global listener via CKEDITOR.on( 'dialogDefinition' ) for each editor. Listener was never removed, thus editor reference was leaking. I've removed this listener on editor destroy.

Codesnippet highlighter

Codesnippet plugin on beforeInit registers method CKEDITOR.plugins.registered.setHighlighter which is bound to editor instance, hence editor reference is leaking. According to docs.

This method can only be called while initialising plugins (in one of the three callbacks).

To fix leak I've forced removal of this method on pluginsLoaded.

Autocomplete panel

Element cke_autocomplete_panel was leaking after each editor creation. It is fixed by calling autocomplete.destroy on editor.destroy.

CKEDITOR.currentInstance

CKEDITOR.currentInstance points at editor even after it was destroyed. Added on destroy listener, to clean that.

skin.js

In skin.jsthere is stored reference to style element for each editor. I've added destroy listener which removes the reference.

Dialog cover

Dialog cover reference is stored in currentCover variable. I've forced removing reference when dialog is hidden.

Testing

For manual testing enable test on your current branch by:
git checkout memory-test tests/_assets/memorytest.html

Then read:
https://github.com/ckeditor/ckeditor-dev/blob/memory-test/tests/core/memory/memory.md

master branch
Screenshot 2019-03-19 at 12 09 34
After applying fix for magicline
Screenshot 2019-03-19 at 12 07 29

Snapshots

  1. After page load.
  2. After creating and destroying first editor.
  3. After creating and destroying second editor.
  4. After 100 next cycles of create-destroy editor.

Notes

  • First editor takes plenty of memory which is not released, but most of it is reused by next editors.
  • Second editor takes more memory than any next editors. I assume that Chrome after second creating of editor performs some optimizations which trade some memory for performance.

Other info

Branch is targeted on major because it uses CKEDITOR.tools.array.find.

Closes #589

@f1ames f1ames self-requested a review March 21, 2019 12:24
@f1ames f1ames self-assigned this Mar 21, 2019
Copy link
Contributor

@f1ames f1ames left a comment

Choose a reason for hiding this comment

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

I have tested the optimizations both in our tool and with CHrome dev console and the improvement is easily visible:

Create - destroy cycles

Before (current master)

Screen Shot 2019-03-21 at 15 55 13

After (this PR)

Screen Shot 2019-03-21 at 16 03 40

First of all the overall memory used after destroying editor instance is lower (~7 vs ~9 MB). Also the difference between consecutive create/destroy cycles is much lower (~0.2 vs >1 MB). Also the overall delta (so the number of elements/resources left after cycles) decreased significantly 👍

Create - edit - destroy cycles

This test included some editing (changing text colors, using widgets, dialogs, dropdowns) between create and destroy. The results are similar:

Before (current master)

Screen Shot 2019-03-21 at 16 00 18

After (this PR)

Screen Shot 2019-03-21 at 16 12 47

Of course the memory usage is bigger than it the create/destroy case but the optimization is clearly visible 👍

I think the overall result is very satisfying for us. As for:

First editor takes plenty of memory which is not released, but most of it is reused by next editors.

I think it is caused by some shared resources and additional scripts which are loaded on editor creation. This resources are reused so we shouldn't try to remove them on destroying editors (because they will need to be loaded again which slows down significantly editor creation).

Second editor takes more memory than any next editors. I assume that Chrome after second creating of editor performs some optimizations which trade some memory for performance.

I'm not 100% sure about this, but it was my assumption too. And the fact that later create/destroy cycles don't increase memory usage almost at all is a good think.


I have added two small comment about test coverage, so you could take a look if we could do something about it. Also it would be good to include this fix in the next minor release (to have it released as fast as possible) so we should do something about CKEDITOR.tools.array.find:

  1. You can port it from major to master.
  2. You can try to do the same without using this method (maybe our array.forEach or array.filter could be useful here)?

The second options should be simpler IMHO.

Last thing, two of the newly added tests are failing on the built version of the editor:

Screen Shot 2019-03-21 at 17 27 26

Looks like some issue with async code:

Screen Shot 2019-03-21 at 17 27 31


// Add stylesheet if missing.
if ( !iframe.getById( 'cke_ui_color' ) ) {
var node = getStylesheet( iframe );
uiColorMenus.push( node );

// Cleanup after destroying editor (#589).
editor.on( 'destroy', function() {
uiColorMenus = CKEDITOR.tools.array.filter( uiColorMenus, function( storedNode ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no unit test for this change. I am not sure how hard it would be to test it (since there are not many skin.js test), but maybe you could give it a try?

Copy link
Contributor

Choose a reason for hiding this comment

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

☝️ Now I have realised that uiColorMenus is just some local var so probably can't be accessed from tests code directly. If that's the case we may skip unit test here.

group = styleGroups[ widgetName ][ groupName ];

// Don't push style if it's already stored (#589).
if ( !CKEDITOR.tools.array.find( group, getCompareFn( style ) ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would be for covering this code with some tests. I know it's local function but maybe saveStyleGroup() can be somehow exposed for testing purposes?

Copy link
Contributor Author

@engineering-this engineering-this Mar 22, 2019

Choose a reason for hiding this comment

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

Even if we made saveStyleGroup public we still don't have access to styleGroups local variable, so we would have to expose also it.
We could expose them both under CKEDITOR.style.customHandlers.widget._. But I wonder is it really worth make them public only for testing purposes?

Copy link
Contributor

Choose a reason for hiding this comment

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

😭 I don't think it is worth the effort, plus I have checked where this styleGroups are used and it's only in removeStylesFromSameGroup() method there which is covered with unit test so if saveStyleGroup() breaks the test should fail too. I think we are covered here 👍

@engineering-this
Copy link
Contributor Author

engineering-this commented Mar 22, 2019

I have added two small comment about test coverage, so you could take a look if we could do something about it. Also it would be good to include this fix in the next minor release (to have it released as fast as possible) so we should do something about CKEDITOR.tools.array.find:

You can port it from major to master.
You can try to do the same without using this method (maybe our array.forEach or array.filter could be useful here)?
The second options should be simpler IMHO.

I'd just copy array.tools.find into private method, we can refactor it as soon as this branch is merged into master, and master into major. Probably reverting the commit on major would be sufficient.

Edit:

Rebased branch on master

Once this is merged into major again as a refactoring just revert commit 145b12b9a.

@f1ames
Copy link
Contributor

f1ames commented Mar 25, 2019

Rebased on latest master.

Copy link
Contributor

@f1ames f1ames left a comment

Choose a reason for hiding this comment

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

The core/loader test fails in IE8:

Screen Shot 2019-03-25 at 13 48 29


I was also thinking about improving the changelog entry but haven't come up with anything better I am afraid. Maybe something like:

Fixed: Editor causes memory leaks in create / destroy cycles.

or

Fixed: Editor does not clean up its resources after destroy causing memory leaks.

@engineering-this engineering-this self-assigned this Mar 25, 2019
@engineering-this
Copy link
Contributor Author

engineering-this commented Mar 25, 2019

Fixed: Editor causes memory leaks in create / destroy cycles.

I like this one more. I didn't want to be too general as it might sound as we fixed all the possible memory leaks, but didn't have any better idea.

Fixed IE failing test by choosing correct event name in same manner as callback function is added.

@f1ames
Copy link
Contributor

f1ames commented Mar 26, 2019

Rebased on latest master.

Copy link
Contributor

@f1ames f1ames left a comment

Choose a reason for hiding this comment

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

LGTM 🎉

@f1ames f1ames merged commit 20caee9 into master Mar 26, 2019
@CKEditorBot CKEditorBot deleted the t/589 branch March 26, 2019 14:44
mixonic added a commit to Addepar/ckeditor-dev that referenced this pull request May 17, 2019
From ckeditor#2969

Excludes changes to:

* plugins/widget/plugin.js - change can't apply cleanly.
* plugins/autocomplete/plugin.js - file doesn't exist on this version.
mixonic added a commit to Addepar/ckeditor-dev that referenced this pull request May 17, 2019
From ckeditor#2969

Excludes changes to:

* plugins/widget/plugin.js - change can't apply cleanly.
* plugins/autocomplete/plugin.js - file doesn't exist on this version.
mixonic added a commit to Addepar/ckeditor-dev that referenced this pull request May 17, 2019
From ckeditor#2969

Excludes changes to:

* plugins/widget/plugin.js - change can't apply cleanly.
* plugins/autocomplete/plugin.js - file doesn't exist on this version.
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.

None yet

2 participants