Fix massive timeout/interval memory leaks

This commit is contained in:
Schuyler Cebulskie
2016-09-19 03:49:42 -04:00
parent 6f7deba4b3
commit 7d8667694d
5 changed files with 41 additions and 28 deletions

View File

@@ -103,8 +103,8 @@ class Client extends EventEmitter {
* @type {?Date} * @type {?Date}
*/ */
this.readyTime = null; this.readyTime = null;
this._intervals = [];
this._timeouts = []; this._timeouts = [];
this._intervals = [];
} }
/** /**
@@ -138,34 +138,18 @@ class Client extends EventEmitter {
destroy() { destroy() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.manager.destroy().then(() => { this.manager.destroy().then(() => {
this._intervals.map(i => clearInterval(i)); for (const i of this._intervals) clearInterval(i);
this._timeouts.map(t => clearTimeout(t)); for (const t of this._timeouts) clearTimeout(t);
this._timeouts = [];
this._intervals = [];
this.token = null; this.token = null;
this.email = null; this.email = null;
this.password = null; this.password = null;
this._timeouts = [];
this._intervals = [];
resolve(); resolve();
}).catch(reject); }).catch(reject);
}); });
} }
setInterval(...params) {
const interval = setInterval(...params);
this._intervals.push(interval);
return interval;
}
setTimeout(...params) {
const restParams = params.slice(1);
const timeout = setTimeout(() => {
this._timeouts.splice(this._timeouts.indexOf(params[0]), 1);
params[0]();
}, ...restParams);
this._timeouts.push(timeout);
return timeout;
}
/** /**
* This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however * This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however
* if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts. * if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts.
@@ -237,6 +221,31 @@ class Client extends EventEmitter {
get status() { get status() {
return this.ws.status; return this.ws.status;
} }
setTimeout(fn, ...params) {
const timeout = setTimeout(() => {
fn();
this._timeouts.splice(this._timeouts.indexOf(timeout), 1);
}, ...params);
this._timeouts.push(timeout);
return timeout;
}
clearTimeout(timeout) {
clearTimeout(timeout);
this._timeouts.splice(this._timeouts.indexOf(timeout), 1);
}
setInterval(...params) {
const interval = setInterval(...params);
this._intervals.push(interval);
return interval;
}
clearInterval(interval) {
clearInterval(interval);
this._intervals.splice(this._intervals.indexOf(interval), 1);
}
} }
module.exports = Client; module.exports = Client;

View File

@@ -31,12 +31,15 @@ class ClientManager {
*/ */
this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`); this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`);
this.client.token = token; this.client.token = token;
const timeout = this.client.setTimeout(() => reject(new Error(Constants.Errors.TOOK_TOO_LONG)), 1000 * 300);
this.client.rest.methods.getGateway().then(gateway => { this.client.rest.methods.getGateway().then(gateway => {
this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`); this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`);
this.client.ws.connect(gateway); this.client.ws.connect(gateway);
this.client.once(Constants.Events.READY, () => resolve(token)); this.client.once(Constants.Events.READY, () => {
resolve(token);
this.client.clearTimeout(timeout);
});
}).catch(reject); }).catch(reject);
this.client.setTimeout(() => reject(new Error(Constants.Errors.TOOK_TOO_LONG)), 1000 * 300);
} }
/** /**

View File

@@ -31,7 +31,7 @@ class VoiceConnectionWebSocket extends EventEmitter {
_shutdown() { _shutdown() {
if (this.ws) this.ws.close(); if (this.ws) this.ws.close();
clearInterval(this.heartbeat); this.voiceConnection.manager.client.clearInterval(this.heartbeat);
} }
_onOpen() { _onOpen() {

View File

@@ -19,7 +19,7 @@ class TypingStartHandler extends AbstractHandler {
typing.lastTimestamp = timestamp; typing.lastTimestamp = timestamp;
typing.resetTimeout(tooLate(channel, user)); typing.resetTimeout(tooLate(channel, user));
} else { } else {
channel._typing.set(user.id, new TypingData(timestamp, timestamp, tooLate(channel, user))); channel._typing.set(user.id, new TypingData(client, timestamp, timestamp, tooLate(channel, user)));
client.emit(Constants.Events.TYPING_START, channel, user); client.emit(Constants.Events.TYPING_START, channel, user);
} }
} }
@@ -27,14 +27,15 @@ class TypingStartHandler extends AbstractHandler {
} }
class TypingData { class TypingData {
constructor(since, lastTimestamp, _timeout) { constructor(client, since, lastTimestamp, _timeout) {
this.client = client;
this.since = since; this.since = since;
this.lastTimestamp = lastTimestamp; this.lastTimestamp = lastTimestamp;
this._timeout = _timeout; this._timeout = _timeout;
} }
resetTimeout(_timeout) { resetTimeout(_timeout) {
clearTimeout(this._timeout); this.client.clearTimeout(this._timeout);
this._timeout = _timeout; this._timeout = _timeout;
} }

View File

@@ -232,7 +232,7 @@ class TextBasedChannel {
const entry = this.client.user._typing.get(this.id); const entry = this.client.user._typing.get(this.id);
entry.count--; entry.count--;
if (entry.count <= 0 || force) { if (entry.count <= 0 || force) {
clearInterval(entry.interval); this.client.clearInterval(entry.interval);
this.client.user._typing.delete(this.id); this.client.user._typing.delete(this.id);
} }
} }