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

Ensure all references are released when Terminal.dispose is called #1525

Merged
merged 18 commits into from
Jun 28, 2018

Conversation

Tyriar
Copy link
Member

@Tyriar Tyriar commented Jun 21, 2018

This PR adds the following:

  • A new abstract class Disposable that is extended by a number of classes and provides a register function to register disposables and a dispose function which disposes them
  • Moves util/Dom.ts to ui/Lifecycle.ts
  • Registered disposables for all DOM event and EventEmitter listeners (excluding some mouse ones which need some more work)
  • Added a dispose terminal button to the bottom of the demo for easier testing of leaked references

After these changes, when a heap snapshot is taken it will no longer show Terminal in the heap. The following was taken after filling the buffer and disposing the terminal:

screen shot 2018-06-21 at 1 32 55 pm

This is what it looked like before (with demo and internal Terminal references holding onto the Terminal):

screen shot 2018-06-21 at 1 33 57 pm

Fixes #1518

@Tyriar Tyriar added this to the 3.5.0 milestone Jun 21, 2018
@Tyriar Tyriar self-assigned this Jun 21, 2018
Copy link
Contributor

@parisk parisk left a comment

Choose a reason for hiding this comment

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

LGTM in general, but I would suggest someone else reviewing it as well, since it deals with a crucial part of the project.

this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)));
this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
this.register(this._renderRowsDebouncer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just an idea; would it make sense to replace this.register(... with TypeScript decorators? This way we could keep disposable properties declared outside the constructor.

Copy link
Member Author

@Tyriar Tyriar Jun 21, 2018

Choose a reason for hiding this comment

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

You mean something like this? (if it works)

function register(target: any, key: string) {
    let _val = this[key];
	
    const getter = () => _val;
    const setter = (newVal) => {
        if (_val) {
            _val.dispose();
        }
        target.register(newVal);
        _val = newVal;
    };
    Object.defineProperty(target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true 
    });
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked into this and I don't think we can do it. For starters decorators are experimental, I switched it on and tried to get it working but it seemed to be acting not as I expected. Maybe we can do this in the future?

demo/main.js Outdated

term.on('paste', function (data, ev) {
term._core,register(term.addDisposableListener('paste', function (data, ev) {
Copy link
Contributor

Choose a reason for hiding this comment

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

term._core,register looks like a typo to me

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

* Registers a disposable object.
* @param d The disposable to register.
*/
public register<T extends IDisposable>(d: T): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think registerDisposable or addDisposable would be better, especially because classes that inherit from Disposable might want to use register with a totally different meaning. Say we have an AddonRegistry class that extends Disposable, it would certainly get very confusing what AddonRegistry.register would do 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about register taking a list of arguments ...disposables, so we could simply register multiple disposables at once, e.g.:

this._terminal.core.register(
  this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()),
  this._terminal.addDisposableListener('linefeed', () => this._lineFeed()),
  this._terminal.addDisposableListener('focus', () => this._focus())
)

Copy link
Member Author

Choose a reason for hiding this comment

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

I think registerDisposable or addDisposable would be better, especially because classes that inherit from Disposable might want to use register with a totally different meaning.

I was trying to make it not so verbose as the lines can already get quite long. This also matches the naming scheme we use in vscode https://github.com/Microsoft/vscode/blob/26af5f5392012067294755ef91cf66fc782b4d48/src/vs/base/common/lifecycle.ts#L70

What do you think about register taking a list of arguments ...disposables, so we could simply register multiple disposables at once, e.g.:

You can already do this with:

this._terminal.core._disposables.push(a, b, c);

I prefer the look of the other way personally

Copy link
Contributor

Choose a reason for hiding this comment

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

Fine with me, your call 👍

@mofux
Copy link
Contributor

mofux commented Jun 25, 2018

@Tyriar Should I give this a final test / review?

@Tyriar
Copy link
Member Author

Tyriar commented Jun 25, 2018

@mofux that would be good, also I think tests are passing it's just that #1531 broke everything

@Tyriar
Copy link
Member Author

Tyriar commented Jun 28, 2018

@mofux I think this is ready for final review, CI failure was unrelated.

@mofux
Copy link
Contributor

mofux commented Jun 28, 2018

Works like a charm, ready to merge from my side 👍

@Tyriar Tyriar merged commit 1395b0f into xtermjs:master Jun 28, 2018
@Tyriar Tyriar deleted the 1518_memory branch June 28, 2018 20:21
@Tyriar
Copy link
Member Author

Tyriar commented Jun 28, 2018

🎉

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.

3 participants