-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Fix for: memory leaks #2969
Conversation
There was a problem hiding this 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
)
After (this PR)
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
)
After (this PR)
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
:
- You can port it from
major
tomaster
. - You can try to do the same without using this method (maybe our
array.forEach
orarray.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:
Looks like some issue with async code:
|
||
// 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 ) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
plugins/widget/plugin.js
Outdated
group = styleGroups[ widgetName ][ groupName ]; | ||
|
||
// Don't push style if it's already stored (#589). | ||
if ( !CKEDITOR.tools.array.find( group, getCompareFn( style ) ) ) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 👍
I'd just copy Edit: Rebased branch on Once this is merged into major again as a refactoring just revert commit |
Rebased on latest |
There was a problem hiding this 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:
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.
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. |
Rebased on latest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🎉
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.
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.
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.
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
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 inCKEDITOR.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 usedstyleDefinition
object for comparisement, buteasyimage
creates them dynamically, so I've changed solution to deeply compare object, to make sure only new styles are stored.Script
onload
andonerror
CKEDITOR.scriptLoader
andCKEDITOR.loader
weren't removingonload
/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 methodCKEDITOR.plugins.registered.setHighlighter
which is bound to editor instance, hence editor reference is leaking. According to docs.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 callingautocomplete.destroy
oneditor.destroy
.CKEDITOR.currentInstance
CKEDITOR.currentInstance
points at editor even after it was destroyed. Added ondestroy
listener, to clean that.skin.js
In
skin.js
there is stored reference tostyle
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
branchAfter applying fix for magicline
Snapshots
Notes
Other info
Branch is targeted on
major
because it usesCKEDITOR.tools.array.find
.Closes #589