question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Undo stack implementation retains unreachable `Value` objects, leaks memory

See original GitHub issue

Do you want to request a feature or report a bug?

Bug

What’s the current behavior?

Open the https://www.slatejs.org/#/history slate example, open the Developer Tools and start a Memory > Allocation Instrumentation on Timeline profile. Then perform the following:

  1. Type 4-5 characters
  2. Undo the typed characters so the undo stack length is 0
  3. Type a character to clear the redo stack
  4. Type another character to remove any remnants of previous props from React’s memoziedProps.

Observe that memory allocated when the original 4-5 characters were typed is still around, even though it is now impossible to reach those operations via undo/redo:

screen shot 2018-11-26 at 1 06 18 pm
  • Note: I’ve verified that disabling undo/redo entirely and it also fixes these leaked allocations, they’re definitely undo/redo related.

What’s the expected behavior?

Slate should not retain memory related to undo/redo operations which can no longer be reached via undo/redo.

Fix

I think this is caused by the fact that the undo redo stack is stored in value.data. Since each undo operation has a copy of value, and value contains an undo stack, Slate creates a recursive structure of Value’s with undo stacks that reference older values, and keeping a handle to a single undo/redo entry is enough to retain the entire stack forever. Clipping to 100 undo entries is nice, but since entry 0’s operation.value.data.get('undos') retains 100 more entries further into the history, it’s not effective at limiting the memory footprint.

After applyOperation applies an undo or redo, the code restores the current undo/redo stack which means that it actually doesn’t need the undo/redo stacks of each saved Value at all. I patched Commands.save to remove these and it resolves the issue:

Commands.save = (editor, operation) => {
  const { operations, value } = editor

  // Remove the undo/redo history from the operation's Value
  operation = operation.withMutations(op => {
    const v2 = op.value.withMutations(v => {
      let d2 = op.value.data
      d2 = d2.remove('undos')
      d2 = d2.remove('redos')
      v.set('data', d2)
    })
    op.set('value', v2)
  })
  
  ...
screen shot 2018-11-26 at 1 05 16 pm

@ianstormtaylor I think my hack above is fairly gross, so I’ve filed this as an issue and not a PR just yet. I actually don’t know much about ImmutableJS and I’m not sure if there’s a better way to accomplish this, or if another fix would be preferable?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:3
  • Comments:8 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
bryanphcommented, Dec 30, 2018

Just a reminder that there is actually a PR open that fixes this from a while ago: https://github.com/ianstormtaylor/slate/pull/2225. It removes the need to keep track of value and makes all ops invertible (when converting from and to JSON).

0reactions
ianstormtaylorcommented, May 9, 2019

This is solved now. Thank you @bryanph!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Does NSUndoManager retain objects? - Stack Overflow
According to the documentation, the prepareWithInvocationTarget: method doesn't retain the arguments passed to it. From the NSUndoManager documentation, ...
Read more >
4 Types of Memory Leaks in JavaScript and How to Get Rid Of ...
The main cause for leaks in garbage collected languages are unwanted references. To understand what unwanted references are, first we need to ...
Read more >
Undo, the art of – Part 1 - Art, Tech and other Nonsense
In this blog post I explore the design of undo/redo and provide some ... and Undo::done(), you may have a few bytes of...
Read more >
Hunting Java Memory Leaks - Toptal
A memory leak occurs when object references that are no longer needed are unnecessarily maintained. These leaks are bad. For one, they put...
Read more >
How To Detect and Prevent Memory Leaks | Scout APM Blog
The above example is likely to cause a memory leak because the variable requests, which holds a new instance of the Map object,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found