prevent memory leaks when using multiple screens. see #157.

This commit is contained in:
Christopher Jeffrey 2015-07-21 19:02:08 -07:00
parent 44017d988b
commit c488c08501
3 changed files with 128 additions and 49 deletions

View File

@ -436,6 +436,10 @@ The screen on which every other node renders.
- __screenshot([xi, xl, yi, yl])__ - Take an SGR screenshot of the screen
within the region. Returns a string containing only characters and SGR codes.
Can be displayed by simply echoing it in a terminal.
- __destroy()__ - destroy the screen object and remove it from the global list.
only useful if using multiple screens.
- __program.destroy()__ - destroy the program object and remove it from the
global list. only useful if using multiple programs.
#### Element (from Node)

View File

@ -31,6 +31,8 @@ function Program(options) {
return new Program(options);
}
Program.bind(this);
EventEmitter.call(this);
if (!options || options.__proto__ !== Object.prototype) {
@ -104,19 +106,6 @@ function Program(options) {
this._buf = '';
this._flush = this.flush.bind(this);
unshiftEvent(process, 'exit', function() {
// Ensure the buffer is flushed (it should
// always be at this point, but who knows).
self.flush();
// Ensure _exiting is set (could technically
// use process._exiting).
self._exiting = true;
});
if (!Program.global) {
Program.global = this;
}
if (options.tput !== false) {
this.setupTput();
}
@ -124,8 +113,41 @@ function Program(options) {
this.listen();
}
Program.global = null;
Program.total = 0;
Program.list = [];
Program.bind = function(program) {
if (!Program.global) {
Program.global = program;
}
if (!~Program.list.indexOf(program)) {
Program.list.push(program);
Program.total++;
}
if (Program._bound) return;
Program._bound = true;
unshiftEvent(process, 'exit', function() {
Program.list.forEach(function(program) {
// Ensure the buffer is flushed (it should
// always be at this point, but who knows).
program.flush();
// Ensure _exiting is set (could technically
// use process._exiting).
program._exiting = true;
});
});
};
Program.prototype.__proto__ = EventEmitter.prototype;
Program.prototype.type = 'program';
Program.prototype.log = function() {
return this._log('LOG', util.format.apply(util, arguments));
};
@ -272,9 +294,8 @@ Program.prototype.listen = function() {
var keys = require('./keys')
, self = this;
if (!this.output.isTTY) {
// TODO: Add an ncurses-like warning.
}
if (this.input._blessedListened) return;
this.input._blessedListened = true;
// unshiftEvent(process, 'exit', function() {
// if (self._originalTitle) {
@ -344,6 +365,9 @@ Program.prototype.listen = function() {
}
});
if (this.output._blessedListened) return;
this.output._blessedListened = true;
// Output
function resize() {
self.cols = self.output.columns;
@ -366,6 +390,18 @@ Program.prototype.listen = function() {
});
};
Program.prototype.destroy = function() {
var index = Program.list.indexOf(this);
if (~index) {
Program.list.splice(index, 1);
Program.total--;
if (Program.total === 0) {
Program.global = null;
}
this.emit('destroy');
}
};
Program.prototype.key = function(key, listener) {
if (typeof key === 'string') key = key.split(/\s*,\s*/);
key.forEach(function(key) {

View File

@ -35,6 +35,8 @@ function Screen(options) {
return new Screen(options);
}
Screen.bind(this);
options = options || {};
if (options.rsety && options.listen) {
options = { program: options };
@ -64,10 +66,6 @@ function Screen(options) {
this.tput = this.program.tput;
if (!Screen.global) {
Screen.global = this;
}
Node.call(this, options);
this.autoPadding = options.autoPadding !== false;
@ -175,35 +173,6 @@ function Screen(options) {
this.setMaxListeners(Infinity);
Screen.total++;
process.on('uncaughtException', function(err) {
if (process.listeners('uncaughtException').length > Screen.total) {
return;
}
self.leave();
err = err || new Error('Uncaught Exception.');
console.error(err.stack ? err.stack + '' : err + '');
nextTick(function() {
process.exit(1);
});
});
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach(function(signal) {
process.on(signal, function() {
if (process.listeners(signal).length > Screen.total) {
return;
}
nextTick(function() {
process.exit(0);
});
});
});
process.on('exit', function() {
self.leave();
});
this.enter();
this.postEnter();
@ -213,6 +182,59 @@ Screen.global = null;
Screen.total = 0;
Screen.list = [];
Screen.signals = true;
Screen.bind = function(screen) {
if (!Screen.global) {
Screen.global = screen;
}
if (!~Screen.list.indexOf(screen)) {
Screen.list.push(screen);
Screen.total++;
}
if (Screen._bound) return;
Screen._bound = true;
process.on('uncaughtException', function(err) {
if (process.listeners('uncaughtException').length > Screen.total) {
return;
}
Screen.list.slice().forEach(function(screen) {
screen.destroy();
});
err = err || new Error('Uncaught Exception.');
console.error(err.stack ? err.stack + '' : err + '');
nextTick(function() {
process.exit(1);
});
});
// XXX Multiple signal handlers and removal of signal
// handlers does not work, so we have this option instead.
if (Screen.signals) {
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach(function(signal) {
process.on(signal, function() {
if (process.listeners(signal).length > Screen.total) {
return;
}
nextTick(function() {
process.exit(0);
});
});
});
}
process.on('exit', function() {
Screen.list.slice().forEach(function(screen) {
screen.destroy();
});
});
};
Screen.prototype.__proto__ = Node.prototype;
Screen.prototype.type = 'screen';
@ -284,6 +306,7 @@ Screen.prototype.postEnter = function() {
var self = this;
if (this.options.debug) {
this.debugLog = new Log({
screen: this,
parent: this,
hidden: true,
draggable: true,
@ -326,6 +349,22 @@ Screen.prototype.postEnter = function() {
}
};
Screen.prototype.destroy = function() {
this.leave();
var index = Screen.list.indexOf(this);
if (~index) {
Screen.list.splice(index, 1);
Screen.total--;
if (Screen.total === 0) {
Screen.global = null;
}
this.emit('destroy');
}
this.program.destroy();
};
Screen.prototype.log = function() {
if (this.debugLog) {
this.debugLog.log.apply(this.debugLog, arguments);