From a414e4884f707098317f437fd5caa7c6c118d577 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 01:28:46 -0500 Subject: [PATCH 01/14] Overhaul sharding --- .eslintrc.json | 6 ++- src/errors/Messages.js | 10 ++-- src/sharding/Shard.js | 91 ++++++++++++++++++++++++++++----- src/sharding/ShardingManager.js | 83 +++++++++++++++--------------- 4 files changed, 132 insertions(+), 58 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8b21a1523..979a9acc1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -127,7 +127,11 @@ "semi-spacing": "error", "semi": "error", "space-before-blocks": "error", - "space-before-function-paren": ["error", "never"], + "space-before-function-paren": ["error", { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + }], "space-in-parens": "error", "space-infix-ops": "error", "space-unary-ops": "error", diff --git a/src/errors/Messages.js b/src/errors/Messages.js index c3116a203..545452703 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -20,9 +20,13 @@ const Messages = { SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.', SHARDING_CHILD_CONNECTION: 'Failed to send message to shard\'s process.', SHARDING_PARENT_CONNECTION: 'Failed to send message to master process.', - SHARDING_NO_SHARDS: 'No shards have been spawned', - SHARDING_IN_PROCESS: 'Shards are still being spawned', - SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards`, + SHARDING_NO_SHARDS: 'No shards have been spawned.', + SHARDING_IN_PROCESS: 'Shards are still being spawned.', + SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`, + SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`, + SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`, + SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`, + SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`, COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', COLOR_CONVERT: 'Unable to convert color to a number.', diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 7e2a51ac7..f71c389df 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -3,6 +3,7 @@ const EventEmitter = require('events'); const path = require('path'); const Util = require('../util/Util'); const { Error } = require('../errors'); +const delayFor = require('util').promisify(setTimeout); /** * Represents a Shard spawned by the ShardingManager. @@ -15,6 +16,7 @@ class Shard extends EventEmitter { */ constructor(manager, id, args = []) { super(); + /** * Manager that created the shard * @type {ShardingManager} @@ -27,6 +29,12 @@ class Shard extends EventEmitter { */ this.id = id; + /** + * Arguments for the shard's process + * @type {string[]} + */ + this.args = args; + /** * Environment variables for the shard's process * @type {Object} @@ -37,20 +45,18 @@ class Shard extends EventEmitter { CLIENT_TOKEN: this.manager.token, }); - /** - * Process of the shard - * @type {ChildProcess} - */ - this.process = childProcess.fork(path.resolve(this.manager.file), args, { - env: this.env, - }).on('message', this._handleMessage.bind(this)); - /** * Whether the shard's {@link Client} is ready * @type {boolean} */ this.ready = false; + /** + * Process of the shard + * @type {?ChildProcess} + */ + this.process = null; + /** * Ongoing promises for calls to {@link Shard#eval}, mapped by the `script` they were called with * @type {Map} @@ -65,11 +71,55 @@ class Shard extends EventEmitter { */ this._fetches = new Map(); - // Handle the death of the process - this.process.once('exit', () => { - this.ready = false; - if (this.manager.respawn) this.manager.createShard(this.id).catch(err => { this.manager.emit('error', err); }); + /** + * Listener function for the {@link ChildProcess}' `exit` event + * @type {Function} + */ + this._exitListener = this._handleExit.bind(this); + } + + /** + * Forks a child process for the shard. + * You should not need to call this manually. + * @param {boolean} [waitForReady=true] Whether to wait until the {@link Client} has become ready before resolving + * @returns {Promise} + */ + async spawn(waitForReady = true) { + if (this.process) throw new Error('SHARDING_PROCESS_EXISTS', this.id); + + this.process = childProcess.fork(path.resolve(this.manager.file), this.args, { env: this.env }) + .on('message', this._handleMessage.bind(this)) + .on('exit', this._exitListener); + + /** + * Emitted upon the creation of the shard's child process. + * @event Shard#spawn + * @param {ChildProcess} process Child process that was created + */ + this.emit('spawn', this.process); + + if (!waitForReady) return this.process; + await new Promise((resolve, reject) => { + this.once('ready', resolve); + this.once('disconnect', () => reject(new Error('SHARDING_READY_DISCONNECTED', this.id))); + this.once('death', () => reject(new Error('SHARDING_READY_DIED', this.id))); + setTimeout(() => reject(new Error('SHARDING_READY_TIMEOUT', this.id)), 30000); }); + return this.process; + } + + /** + * Kills and restarts the shard's process. + * @param {number} [delay=500] How long to wait between killing the process and restarting it (in milliseconds) + * @param {boolean} [waitForReady=true] Whether to wait the {@link Client} has become ready before resolving + * @returns {Promise} + */ + async respawn(delay = 500, waitForReady = true) { + this.process.removeListener('exit', this._exitListener); + this.process.kill(); + this._handleExit(false); + if (delay > 0) await delayFor(delay); + return this.spawn(waitForReady); } /** @@ -215,6 +265,23 @@ class Shard extends EventEmitter { */ this.manager.emit('message', this, message); } + + /** + * Handles the shard's process exiting. + * @param {boolean} [respawn=this.manager.respawn] Whether to spawn the shard again + * @private + */ + _handleExit(respawn = this.manager.respawn) { + /** + * Emitted upon the shard's child process exiting. + * @event Shard#death + * @param {ChildProcess} process Child process that exited + */ + this.emit('death', this.process); + this.ready = false; + this.process = null; + if (respawn) this.spawn().catch(err => this.emit('error', err)); + } } module.exports = Shard; diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index 117fca4f9..0c7de0ab0 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -5,6 +5,7 @@ const Shard = require('./Shard'); const Collection = require('../util/Collection'); const Util = require('../util/Util'); const { Error, TypeError, RangeError } = require('../errors'); +const delayFor = require('util').promisify(setTimeout); /** * This is a utility class that can be used to help you spawn shards of your client. Each shard is completely separate @@ -82,33 +83,32 @@ class ShardingManager extends EventEmitter { /** * Spawns a single shard. - * @param {number} id The ID of the shard to spawn. **This is usually not necessary** - * @returns {Promise} + * @param {number} id ID of the shard to spawn. **This is usually not necessary** + * @returns {Shard} */ createShard(id = this.shards.size) { const shard = new Shard(this, id, this.shardArgs); this.shards.set(id, shard); /** - * Emitted upon launching a shard. + * Emitted upon creating a shard. * @event ShardingManager#launch - * @param {Shard} shard Shard that was launched + * @param {Shard} shard Shard that was created */ this.emit('launch', shard); - return Promise.resolve(shard); + return shard; } /** * Spawns multiple shards. * @param {number} [amount=this.totalShards] Number of shards to spawn - * @param {number} [delay=7500] How long to wait in between spawning each shard (in milliseconds) + * @param {number} [delay=5500] How long to wait in between spawning each shard (in milliseconds) + * @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another * @returns {Promise>} */ - spawn(amount = this.totalShards, delay = 7500) { + async spawn(amount = this.totalShards, delay = 5500, waitForReady = true) { + // Obtain/verify the number of shards to spawn if (amount === 'auto') { - return Util.fetchRecommendedShards(this.token).then(count => { - this.totalShards = count; - return this._spawn(count, delay); - }); + amount = await Util.fetchRecommendedShards(this.token); } else { if (typeof amount !== 'number' || isNaN(amount)) { throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.'); @@ -117,41 +117,22 @@ class ShardingManager extends EventEmitter { if (amount !== Math.floor(amount)) { throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.'); } - return this._spawn(amount, delay); } - } - /** - * Actually spawns shards, unlike that poser above >:( - * @param {number} amount Number of shards to spawn - * @param {number} delay How long to wait in between spawning each shard (in milliseconds) - * @returns {Promise>} - * @private - */ - _spawn(amount, delay) { - return new Promise(resolve => { - if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size); - this.totalShards = amount; + // Make sure this many shards haven't already been spawned + if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size); + this.totalShards = amount; - this.createShard(); - if (this.shards.size >= this.totalShards) { - resolve(this.shards); - return; - } + // Spawn the shards + for (let s = 1; s <= amount; s++) { + const promises = []; + const shard = this.createShard(); + promises.push(shard.spawn(waitForReady)); + if (delay > 0 && s !== amount) promises.push(delayFor(delay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } - if (delay <= 0) { - while (this.shards.size < this.totalShards) this.createShard(); - resolve(this.shards); - } else { - const interval = setInterval(() => { - this.createShard(); - if (this.shards.size >= this.totalShards) { - clearInterval(interval); - resolve(this.shards); - } - }, delay); - } - }); + return this.shards; } /** @@ -194,6 +175,24 @@ class ShardingManager extends EventEmitter { for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop)); return Promise.all(promises); } + + /** + * Kills all running shards and respawns them. + * @param {number} [shardDelay=5000] How long to wait between shards (in milliseconds) + * @param {number} [respawnDelay=500] How long to wait between killing a shard's process and restarting it + * (in milliseconds) + * @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another + * @returns {Promise>} + */ + async respawn(shardDelay = 5000, respawnDelay = 500, waitForReady = true) { + let s = 0; + for (const shard of this.shards) { + const promises = [shard.respawn(respawnDelay, waitForReady)]; + if (++s < this.shards.size && shardDelay > 0) promises.push(delayFor(shardDelay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } + return this.shards; + } } module.exports = ShardingManager; From 2a332d8d15b0f06d8fcc236a4aab0aeff0277d1f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 01:54:10 -0500 Subject: [PATCH 02/14] Add ShardClientUtil#respawnAll --- src/sharding/Shard.js | 9 +++++++++ src/sharding/ShardClientUtil.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index f71c389df..6ae4cc600 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -255,6 +255,15 @@ class Shard extends EventEmitter { ); return; } + + // Shard is requesting a respawn of all shards + if (message._sRespawnAll) { + const { shardDelay, respawnDelay, waitForReady } = message._sRespawnAll; + this.manager.respawn(shardDelay, respawnDelay, waitForReady).catch(() => { + // Do nothing + }); + return; + } } /** diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index 70e625824..df1bc8341 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -59,6 +59,7 @@ class ShardClientUtil { * console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`); * }) * .catch(console.error); + * @see {@link ShardingManager#fetchClientValues} */ fetchClientValues(prop) { return new Promise((resolve, reject) => { @@ -80,6 +81,7 @@ class ShardClientUtil { * Evaluates a script on all shards, in the context of the Clients. * @param {string} script JavaScript to run on each shard * @returns {Promise>} Results of the script execution + * @see {@link ShardingManager#broadcastEval} */ broadcastEval(script) { return new Promise((resolve, reject) => { @@ -97,6 +99,19 @@ class ShardClientUtil { }); } + /** + * Requests a respawn of all shards. + * @param {number} [shardDelay=5000] How long to wait between shards (in milliseconds) + * @param {number} [respawnDelay=500] How long to wait between killing a shard's process and restarting it + * (in milliseconds) + * @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another + * @returns {Promise} Resolves upon the message being sent + * @see {@link ShardingManager#respawn} + */ + respawnAll(shardDelay = 5000, respawnDelay = 500, waitForReady = true) { + return this.send({ _sRespawnAll: { shardDelay, respawnDelay, waitForReady } }); + } + /** * Handles an IPC message. * @param {*} message Message received From 637ea09532e1b88204680bb78a443972d952a580 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 01:56:51 -0500 Subject: [PATCH 03/14] Fix lint error --- src/client/voice/util/Secretbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/voice/util/Secretbox.js b/src/client/voice/util/Secretbox.js index 31f5b8d00..b21fb8f9d 100644 --- a/src/client/voice/util/Secretbox.js +++ b/src/client/voice/util/Secretbox.js @@ -15,7 +15,7 @@ const libs = { exports.methods = {}; -(async() => { +(async () => { for (const libName of Object.keys(libs)) { try { const lib = require(libName); From f777c19fbf3e770cc54531241c24ec19e6ae9952 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 02:03:44 -0500 Subject: [PATCH 04/14] Fix naming conflict with ShardingManager#respawn --- src/sharding/Shard.js | 2 +- src/sharding/ShardClientUtil.js | 2 +- src/sharding/ShardingManager.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 6ae4cc600..cbe85110b 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -259,7 +259,7 @@ class Shard extends EventEmitter { // Shard is requesting a respawn of all shards if (message._sRespawnAll) { const { shardDelay, respawnDelay, waitForReady } = message._sRespawnAll; - this.manager.respawn(shardDelay, respawnDelay, waitForReady).catch(() => { + this.manager.respawnAll(shardDelay, respawnDelay, waitForReady).catch(() => { // Do nothing }); return; diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index df1bc8341..b62cb36c4 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -106,7 +106,7 @@ class ShardClientUtil { * (in milliseconds) * @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another * @returns {Promise} Resolves upon the message being sent - * @see {@link ShardingManager#respawn} + * @see {@link ShardingManager#respawnAll} */ respawnAll(shardDelay = 5000, respawnDelay = 500, waitForReady = true) { return this.send({ _sRespawnAll: { shardDelay, respawnDelay, waitForReady } }); diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index 0c7de0ab0..85b2f2c67 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -184,7 +184,7 @@ class ShardingManager extends EventEmitter { * @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another * @returns {Promise>} */ - async respawn(shardDelay = 5000, respawnDelay = 500, waitForReady = true) { + async respawnAll(shardDelay = 5000, respawnDelay = 500, waitForReady = true) { let s = 0; for (const shard of this.shards) { const promises = [shard.respawn(respawnDelay, waitForReady)]; From 9cd097492c8a6cc2fe834d28fb31d4ad46054965 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 02:06:38 -0500 Subject: [PATCH 05/14] Update doc for ShardingManager#createShard id parameter --- src/sharding/ShardingManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index 85b2f2c67..bbe5e7724 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -83,7 +83,8 @@ class ShardingManager extends EventEmitter { /** * Spawns a single shard. - * @param {number} id ID of the shard to spawn. **This is usually not necessary** + * @param {number} [id=this.shards.size] ID of the shard to spawn - + * **This is usually not necessary to manually specify.** * @returns {Shard} */ createShard(id = this.shards.size) { From 975da5f1a52e821a9409fe15f64b3ba4bd387520 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 02:21:29 -0500 Subject: [PATCH 06/14] Rewrite sharding class descriptions and link Client --- src/sharding/Shard.js | 5 +++-- src/sharding/ShardClientUtil.js | 5 +++-- src/sharding/ShardingManager.js | 11 +++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index cbe85110b..b54701f40 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -6,7 +6,8 @@ const { Error } = require('../errors'); const delayFor = require('util').promisify(setTimeout); /** - * Represents a Shard spawned by the ShardingManager. + * A self-contained shard spawned by the {@link ShardingManager}. + * @extends EventEmitter */ class Shard extends EventEmitter { /** @@ -171,7 +172,7 @@ class Shard extends EventEmitter { } /** - * Evaluates a script on the shard, in the context of the client. + * Evaluates a script on the shard, in the context of the {@link Client}. * @param {string} script JavaScript to run on the shard * @returns {Promise<*>} Result of the script execution */ diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index b62cb36c4..b0e9d57ed 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -3,7 +3,8 @@ const { Events } = require('../util/Constants'); const { Error } = require('../errors'); /** - * Helper class for sharded clients spawned as a child process, such as from a ShardingManager. + * Helper class for sharded clients spawned as a child process, such as from a {@link ShardingManager}. + * Utilises IPC to send and receive data to/from the master process and other shards. */ class ShardClientUtil { /** @@ -78,7 +79,7 @@ class ShardClientUtil { } /** - * Evaluates a script on all shards, in the context of the Clients. + * Evaluates a script on all shards, in the context of the {@link Clients}. * @param {string} script JavaScript to run on each shard * @returns {Promise>} Results of the script execution * @see {@link ShardingManager#broadcastEval} diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index bbe5e7724..4b0ad7b87 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -8,9 +8,12 @@ const { Error, TypeError, RangeError } = require('../errors'); const delayFor = require('util').promisify(setTimeout); /** - * This is a utility class that can be used to help you spawn shards of your client. Each shard is completely separate - * from the other. The Shard Manager takes a path to a file and spawns it under the specified amount of shards safely. - * If you do not select an amount of shards, the manager will automatically decide the best amount. + * This is a utility class that makes multi-process sharding of a bot an easy and painless experience. + * It works by spawning a self-contained {@link ChildProcess} for each individual shard, each containing its own client. + * They all have a line of communication with the master process, and there are several useful methods that utilise + * it in order to simplify tasks that are normally difficult with multi-process sharding. It can spawn a specific number + * of shards or the amount that Discord suggests for the bot, and takes a path to your main bot script to launch for + * each one. * @extends {EventEmitter} */ class ShardingManager extends EventEmitter { @@ -148,7 +151,7 @@ class ShardingManager extends EventEmitter { } /** - * Evaluates a script on all shards, in the context of the Clients. + * Evaluates a script on all shards, in the context of the {@link Client}s. * @param {string} script JavaScript to run on each shard * @returns {Promise>} Results of the script execution */ From 1338e9bd8e2a29ebe2ce9b45bc651adc1bf5601f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 02:30:20 -0500 Subject: [PATCH 07/14] Update sharding docs some more --- src/sharding/Shard.js | 4 +++- src/sharding/ShardingManager.js | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index b54701f40..cff80ad38 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -6,7 +6,9 @@ const { Error } = require('../errors'); const delayFor = require('util').promisify(setTimeout); /** - * A self-contained shard spawned by the {@link ShardingManager}. + * A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains + * an instance of the bot and its {@link Client}. When its child process exits for any reason, the shard will spawn a + * new one to replace it as necessary. * @extends EventEmitter */ class Shard extends EventEmitter { diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index 4b0ad7b87..f682c5887 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -9,11 +9,11 @@ const delayFor = require('util').promisify(setTimeout); /** * This is a utility class that makes multi-process sharding of a bot an easy and painless experience. - * It works by spawning a self-contained {@link ChildProcess} for each individual shard, each containing its own client. - * They all have a line of communication with the master process, and there are several useful methods that utilise - * it in order to simplify tasks that are normally difficult with multi-process sharding. It can spawn a specific number - * of shards or the amount that Discord suggests for the bot, and takes a path to your main bot script to launch for - * each one. + * It works by spawning a self-contained {@link ChildProcess} for each individual shard, each containing its own + * instance of your bot's {@link Client}. They all have a line of communication with the master process, and there are + * several useful methods that utilise it in order to simplify tasks that are normally difficult with sharding. It can + * spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a path to your main bot + * script to launch for each one. * @extends {EventEmitter} */ class ShardingManager extends EventEmitter { From acf82f32c3d25608ec0bcd03f095242cf937882a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 02:31:06 -0500 Subject: [PATCH 08/14] Mark Shard#_exitListener as private --- src/sharding/Shard.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index cff80ad38..e5c59b395 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -77,6 +77,7 @@ class Shard extends EventEmitter { /** * Listener function for the {@link ChildProcess}' `exit` event * @type {Function} + * @private */ this._exitListener = this._handleExit.bind(this); } From 26b28813a83aa5888419e6538ff35d1bf3dae106 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 19 Nov 2017 13:47:04 -0500 Subject: [PATCH 09/14] Use a custom promisified setTimeout --- src/sharding/Shard.js | 3 +-- src/sharding/ShardingManager.js | 5 ++--- src/util/Util.js | 12 ++++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index e5c59b395..044882bec 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'); const path = require('path'); const Util = require('../util/Util'); const { Error } = require('../errors'); -const delayFor = require('util').promisify(setTimeout); /** * A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains @@ -122,7 +121,7 @@ class Shard extends EventEmitter { this.process.removeListener('exit', this._exitListener); this.process.kill(); this._handleExit(false); - if (delay > 0) await delayFor(delay); + if (delay > 0) await Util.delayFor(delay); return this.spawn(waitForReady); } diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index f682c5887..e8e8f8706 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -5,7 +5,6 @@ const Shard = require('./Shard'); const Collection = require('../util/Collection'); const Util = require('../util/Util'); const { Error, TypeError, RangeError } = require('../errors'); -const delayFor = require('util').promisify(setTimeout); /** * This is a utility class that makes multi-process sharding of a bot an easy and painless experience. @@ -132,7 +131,7 @@ class ShardingManager extends EventEmitter { const promises = []; const shard = this.createShard(); promises.push(shard.spawn(waitForReady)); - if (delay > 0 && s !== amount) promises.push(delayFor(delay)); + if (delay > 0 && s !== amount) promises.push(Util.delayFor(delay)); await Promise.all(promises); // eslint-disable-line no-await-in-loop } @@ -192,7 +191,7 @@ class ShardingManager extends EventEmitter { let s = 0; for (const shard of this.shards) { const promises = [shard.respawn(respawnDelay, waitForReady)]; - if (++s < this.shards.size && shardDelay > 0) promises.push(delayFor(shardDelay)); + if (++s < this.shards.size && shardDelay > 0) promises.push(Util.delayFor(shardDelay)); await Promise.all(promises); // eslint-disable-line no-await-in-loop } return this.shards; diff --git a/src/util/Util.js b/src/util/Util.js index b1de917be..d92dc94b7 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -366,6 +366,18 @@ class Util { return dec; } + + /** + * Creates a Promise that resolves after a specified duration. + * @param {number} ms How long to wait before resolving (in milliseconds) + * @returns {Promise} + * @private + */ + static delayFor(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + } } module.exports = Util; From b5459a96fab428eb828f2c25a45038b301369cc5 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 20 Nov 2017 22:20:32 -0500 Subject: [PATCH 10/14] Move ShardingManager#message event to Shard#message --- src/sharding/Shard.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 044882bec..4cb9a55df 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -270,12 +270,11 @@ class Shard extends EventEmitter { } /** - * Emitted upon recieving a message from a shard. - * @event ShardingManager#message - * @param {Shard} shard Shard that sent the message + * Emitted upon recieving a message from the child process. + * @event Shard#message * @param {*} message Message that was received */ - this.manager.emit('message', this, message); + this.emit('message', message); } /** From c447abad60b4e37c61e0d5386159c7a8121ed208 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 20 Nov 2017 22:26:14 -0500 Subject: [PATCH 11/14] Clear evals and fetches on process death --- src/sharding/Shard.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 4cb9a55df..5bb442919 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -291,6 +291,8 @@ class Shard extends EventEmitter { this.emit('death', this.process); this.ready = false; this.process = null; + this._evals.clear(); + this._fetches.clear(); if (respawn) this.spawn().catch(err => this.emit('error', err)); } } From 527c729aca3dd657eb46b5bdb5415bc98e82222a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 20 Nov 2017 22:29:46 -0500 Subject: [PATCH 12/14] Possibly fix weird behaviour --- src/sharding/Shard.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 5bb442919..a91203f2c 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -282,17 +282,20 @@ class Shard extends EventEmitter { * @param {boolean} [respawn=this.manager.respawn] Whether to spawn the shard again * @private */ - _handleExit(respawn = this.manager.respawn) { + _handleExit(respawn) { + if (typeof respawn === 'undefined') respawn = this.manager.respawn; /** * Emitted upon the shard's child process exiting. * @event Shard#death * @param {ChildProcess} process Child process that exited */ this.emit('death', this.process); + this.ready = false; this.process = null; this._evals.clear(); this._fetches.clear(); + if (respawn) this.spawn().catch(err => this.emit('error', err)); } } From c6244ee6e1401aa1146bc8f857f8989fa3d19b44 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 20 Nov 2017 22:37:35 -0500 Subject: [PATCH 13/14] Fix shards not respawning on exit --- src/sharding/Shard.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index a91203f2c..0e44f91a2 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -78,7 +78,7 @@ class Shard extends EventEmitter { * @type {Function} * @private */ - this._exitListener = this._handleExit.bind(this); + this._exitListener = this._handleExit.bind(this, undefined); } /** @@ -282,8 +282,7 @@ class Shard extends EventEmitter { * @param {boolean} [respawn=this.manager.respawn] Whether to spawn the shard again * @private */ - _handleExit(respawn) { - if (typeof respawn === 'undefined') respawn = this.manager.respawn; + _handleExit(respawn = this.manager.respawn) { /** * Emitted upon the shard's child process exiting. * @event Shard#death From 0d188c0fba3ecd5c247e1d9269b42297c3f83eb8 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 24 Nov 2017 22:33:29 -0500 Subject: [PATCH 14/14] Rename ShardingManager#launch event to shardCreate --- src/sharding/ShardingManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index e8e8f8706..9596ead2e 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -94,10 +94,10 @@ class ShardingManager extends EventEmitter { this.shards.set(id, shard); /** * Emitted upon creating a shard. - * @event ShardingManager#launch + * @event ShardingManager#shardCreate * @param {Shard} shard Shard that was created */ - this.emit('launch', shard); + this.emit('shardCreate', shard); return shard; }