Proper shutdown sequence, upgrade logger

This commit is contained in:
Erik 2023-12-06 13:09:51 +02:00
parent 9d88266f41
commit e2d8e31588
13 changed files with 1105 additions and 1073 deletions

View File

@ -26,7 +26,7 @@
"dependencies": {
"@discordjs/collection": "^1.5.1",
"@discordjs/rest": "^1.7.1",
"@navy.gif/logger": "^2.5.2",
"@navy.gif/logger": "^2.5.3",
"@navy.gif/timestring": "^6.0.6",
"@types/node": "^18.15.11",
"chalk": "^5.3.0",

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,7 @@ class EventHooker
this.#logger.debug(`Setting up handler for ${eventName}`);
this.#target.on(eventName, async (...args) =>
{
if (!this.#target.ready && !this.#safeEvents.includes(eventName))
if (!this.#target.ready && !this.#safeEvents.includes(eventName))
{
this.#logger.warn(`Client not ready to handle events, event: ${eventName}`);
return;

View File

@ -12,12 +12,12 @@ class Intercom
{
this.#client.eventHooker.hook('built', () =>
{
this._transportCommands();
this.#transportCommands();
});
}
}
send (type: string, message = {})
send (type: string, message = {})
{
if (typeof message !== 'object')
throw new Error('Invalid message object');
@ -29,7 +29,7 @@ class Intercom
});
}
_transportCommands ()
#transportCommands ()
{
if (!this.#client.application)
throw new Error('Missing client application');

View File

@ -358,7 +358,7 @@ class MuteSetting extends Setting
}
catch (err)
{
this.client.logger.error(err);
this.client.logger.error(err as Error);
}
}

View File

@ -71,7 +71,7 @@ class GuildWrapper
throw new Error('Already a wrapper');
this.#client = client;
this.#logger = client.createLogger({ name: `Guild: ${guild.id}` });
this.#logger = client.createLogger(this, { name: `Guild: ${guild.id}` });
this.#guild = guild;
this.#webhooks = new Collection();
this.#memberWrappers = new Collection();

View File

@ -50,11 +50,11 @@ class StorageManager
return this;
}
async destroy ()
async close ()
{
const keys = Object.keys(this.#providers);
for (const provider of keys)
await this.#providers[provider].destroy();
await this.#providers[provider].close();
}
_getName (instance: Provider | Table)

View File

@ -115,7 +115,7 @@ abstract class Provider implements Initialisable
}
abstract destroy(): Promise<void>;
abstract close(): Promise<void>;
get name ()
{

View File

@ -126,7 +126,7 @@ class MariaDBProvider extends Provider
}
async destroy ()
async close ()
{
this.logger.status('Shutting down database connections');
if (!this.ready)

View File

@ -61,12 +61,13 @@ class MongoDBProvider extends Provider
this.logger.info('DB connected');
}
async destroy ()
async close ()
{
if (!this.initialised)
return this.logger.warn('Database already closed');
this.logger.status('Closing DB connection');
await this.#client?.close();
this.#client?.removeAllListeners();
this._initialised = false;
this.#db = null;
this.logger.status('Database closed');

View File

@ -1,375 +1,391 @@
import { EventEmitter } from 'node:events';
import { inspect } from 'node:util';
import path from 'node:path';
import { CommandsDef, IPCMessage } from '../../@types/Shared.js';
import { BroadcastEvalOptions, ShardMethod, ShardingOptions } from '../../@types/Shard.js';
import { ControllerOptions } from '../../@types/Controller.js';
import { MasterLogger } from '@navy.gif/logger';
import { Collection } from 'discord.js';
// Available for evals
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import ClientUtils from './ClientUtils.js';
import Metrics from './Metrics.js';
// import ApiClientUtil from './ApiClientUtil.js';
import SlashCommandManager from './rest/SlashCommandManager.js';
import { Shard } from './shard/index.js';
import { existsSync } from 'node:fs';
import Util from '../utilities/Util.js';
// Placeholder
type GalacticAPI = {
init: () => Promise<void>
}
class Controller extends EventEmitter
{
// #shardingManager: ShardingManager;
#slashCommandManager: SlashCommandManager;
#logger: MasterLogger;
#metrics: Metrics;
#options: ControllerOptions;
#shardingOptions: ShardingOptions;
// #apiClientUtil: ApiClientUtil;
#shards: Collection<number, Shard>;
#version: string;
#readyAt: number | null;
#built: boolean;
#api?: GalacticAPI;
constructor (options: ControllerOptions, version: string)
{
super();
// Sharding
const respawn = process.env.NODE_ENV !== 'development';
const clientPath = path.join(options.rootDir, 'client/DiscordClient.js');
if (!existsSync(clientPath))
throw new Error(`Client path does not seem to exist: ${clientPath}`);
this.#options = options;
const { shardList, totalShards } = Controller.parseShardOptions(options.shardOptions);
options.discord.rootDir = options.rootDir;
options.discord.logger = options.logger;
options.discord.storage = options.storage;
options.discord.version = version;
this.#shardingOptions = {
path: clientPath,
totalShards,
shardList,
respawn,
shardArgs: [],
execArgv: [],
token: process.env.DISCORD_TOKEN,
clientOptions: options.discord,
};
// Other
this.#slashCommandManager = new SlashCommandManager(this);
this.#logger = new MasterLogger(options.logger);
this.#metrics = new Metrics(this);
// this.#apiClientUtil = new ApiClientUtil(this);
this.#version = version;
this.#readyAt = null;
this.#built = false;
this.#shards = new Collection();
// this.#shardingManager.on('message', this._handleMessage.bind(this));
}
get version ()
{
return this.#version;
}
get ready ()
{
return this.#built;
}
get readyAt ()
{
return this.#readyAt || -1;
}
get totalShards ()
{
return this.#shardingOptions.totalShards as number;
}
get developerGuilds ()
{
return this.#options.discord.slashCommands?.developerGuilds;
}
get logger ()
{
return this.#logger;
}
get api ()
{
return this.#api;
}
get shards ()
{
return this.#shards.clone();
}
async build ()
{
const start = Date.now();
// const API = this._options.api.load ? await import('/Documents/My programs/GBot/api/index.js')
// .catch(() => this.logger.warn(`Error importing API files, continuing without`)) : null;
// let API = null;
// if (this.#options.api.load)
// API = await import('../../api/index.js').catch(() => this.#logger.warn(`Error importing API files, continuing without`));
// if (API) {
// // TODO: this needs to be fixed up
// this.#logger.info('Booting up API');
// const { default: APIManager } = API;
// this.#api = new APIManager(this, this.#options.api) as GalacticAPI;
// await this.#api.init();
// const now = Date.now();
// this.#logger.info(`API ready. Took ${now - start} ms`);
// start = now;
// }
this.#logger.status('Starting bot shards');
// await this.shardingManager.spawn().catch((error) => {
// this.#logger.error(`Fatal error during shard spawning:\n${error.stack || inspect(error)}`);
// // eslint-disable-next-line no-process-exit
// process.exit(); // Prevent a boot loop when shards die due to an error in the client
// });
const { totalShards, token } = this.#shardingOptions;
let shardCount = 0;
if (totalShards === 'auto')
{
if (!token)
throw new Error('Missing token');
shardCount = await Util.fetchRecommendedShards(token);
}
else
{
if (typeof shardCount !== 'number' || isNaN(shardCount))
throw new TypeError('Amount of shards must be a number.');
if (shardCount < 1)
throw new RangeError('Amount of shards must be at least one.');
if (!Number.isInteger(shardCount))
throw new TypeError('Amount of shards must be an integer.');
}
const promises = [];
for (let i = 0; i < shardCount; i++)
{
const shard = this.createShard(shardCount);
promises.push(shard.spawn());
}
await Promise.all(promises);
this.#logger.status(`Shards spawned, spawned ${this.#shards.size} shards. Took ${Date.now() - start} ms`);
this.#built = true;
this.#readyAt = Date.now();
}
createShard (totalShards: number)
{
const ids = this.#shards.map(s => s.id);
const id = ids.length ? Math.max(...ids) + 1 : 0;
const { path: file, token, respawn, execArgv, shardArgs: args, clientOptions: discordOptions } = this.#shardingOptions;
if (!file)
throw new Error('File seems to be missing');
if (!discordOptions)
throw new Error('Missing discord options');
const shard = new Shard(this, id, {
file,
token,
respawn,
args,
execArgv,
totalShards,
clientOptions: discordOptions
});
this.#shards.set(shard.id, shard);
this.#logger.attach(shard);
this.#setListeners(shard);
return shard;
}
#setListeners (shard: Shard)
{
shard.on('death', () => this.#logger.info(`Shard ${shard.id} has died`));
shard.on('fatal', ({ error }) => this.#logger.warn(`Shard ${shard.id} has died fatally: ${inspect(error) ?? ''}`));
shard.on('shutdown', () => this.#logger.info(`Shard ${shard.id} is shutting down gracefully`));
shard.on('ready', () => this.#logger.info(`Shard ${shard.id} is ready`));
shard.on('disconnect', () => this.#logger.warn(`Shard ${shard.id} has disconnected`));
shard.on('processDisconnect', () => this.#logger.warn(`Process for ${shard.id} has disconnected`));
shard.on('spawn', () => this.#logger.info(`Shard ${shard.id} spawned`));
shard.on('error', (err) => this.#logger.error(`Shard ${shard.id} ran into an error:\n${err.stack}`));
shard.on('warn', (msg) => this.#logger.warn(`Warning from shard ${shard.id}: ${msg}`, { broadcast: true }));
shard.on('message', (msg) => this.#handleMessage(shard, msg));
}
#handleMessage (shard: Shard, message: IPCMessage)
{
if (message._logger)
return;
// this.logger.debug(`New message from ${shard ? `${message._api ? 'api-' : ''}shard ${shard.id}`: 'manager'}: ${inspect(message)}`);
if (message._mEval)
return this.eval(shard, { script: message._mEval, debug: message.debug || false });
if (message._commands)
return this.#slashCommandManager._handleMessage(message as CommandsDef);
if (message._api)
return this.apiRequest(shard, message);
}
apiRequest (shard: Shard, message: IPCMessage)
{
const { type } = message;
switch (type)
{
case 'stats':
this.#metrics.aggregateStatistics(shard, message);
break;
default:
// this.#apiClientUtil.handleMessage(shard, message);
}
}
/**
* @param {*} shard The shard from which the eval came and to which it will be returned
* @param {*} script The script to be executed
* @memberof Manager
* @private
*/
async eval (shard: Shard, { script, debug }: {script: string, debug: boolean})
{
this.#logger.info(`Incoming manager eval from shard ${shard.id}:\n${script}`);
let result = null,
error = null;
const response: IPCMessage = {
script, _mEvalResult: true
};
try
{
// eslint-disable-next-line no-eval
result = await eval(script);
response._result = result;
// if(typeof result !== 'string') result = inspect(result);
if (debug)
this.#logger.debug(`Eval result: ${inspect(result)}`);
}
catch (e)
{
const err = e as Error;
error = Util.makePlainError(err);
response._error = error;
}
return shard.send(response);
}
// eslint-disable-next-line @typescript-eslint/ban-types
broadcastEval (script: string | Function, options: BroadcastEvalOptions = {})
{
if (typeof script !== 'function')
return Promise.reject(new TypeError('[shardmanager] Provided eval must be a function.'));
return this._performOnShards('eval', [ `(${script})(this, ${JSON.stringify(options.context)})` ], options.shard);
}
fetchClientValues (prop: string, shard?: number)
{
return this._performOnShards('fetchClientValue', [ prop ], shard);
}
_performOnShards (method: ShardMethod, args: [string, object?], shard?: number): Promise<unknown>
{
if (this.#shards.size === 0)
return Promise.reject(new Error('No shards available.'));
if (!this.ready)
return Promise.reject(new Error('Controller not ready'));
if (typeof shard === 'number')
{
if (!this.#shards.has(shard))
Promise.reject(new Error('Shard not found.'));
const s = this.#shards.get(shard) as Shard;
if (method === 'eval')
return s.eval(...args);
else if (method === 'fetchClientValue')
return s.eval(args[0]);
}
const promises = [];
for (const sh of this.#shards.values())
{
if (method === 'eval')
promises.push(sh.eval(...args));
else if (method === 'fetchClientValue')
promises.push(sh.eval(args[0]));
}
return Promise.all(promises);
}
async respawnAll ({ shardDelay = 5000, respawnDelay = 500, timeout = 30000 } = {})
{
let s = 0;
for (const shard of this.#shards.values())
{
const promises: Promise<unknown>[] = [ shard.respawn({ delay: respawnDelay, timeout }) ];
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;
}
static parseShardOptions (options: ShardingOptions)
{
let shardList = options.shardList ?? 'auto';
if (shardList !== 'auto')
{
if (!Array.isArray(shardList))
throw new Error('ShardList must be an array.');
shardList = [ ...new Set(shardList) ];
if (shardList.length < 1)
throw new Error('ShardList must have at least one ID.');
if (shardList.some((shardId) => typeof shardId !== 'number' || isNaN(shardId) || !Number.isInteger(shardId) || shardId < 0))
throw new Error('ShardList must be an array of positive integers.');
}
const totalShards = options.totalShards || 'auto';
if (totalShards !== 'auto')
{
if (typeof totalShards !== 'number' || isNaN(totalShards))
throw new Error('TotalShards must be an integer.');
if (totalShards < 1)
throw new Error('TotalShards must be at least one.');
if (!Number.isInteger(totalShards))
throw new Error('TotalShards must be an integer.');
}
return { shardList, totalShards };
}
}
import { EventEmitter } from 'node:events';
import { inspect } from 'node:util';
import path from 'node:path';
import { CommandsDef, IPCMessage } from '../../@types/Shared.js';
import { BroadcastEvalOptions, ShardMethod, ShardingOptions } from '../../@types/Shard.js';
import { ControllerOptions } from '../../@types/Controller.js';
import { MasterLogger } from '@navy.gif/logger';
import { Collection } from 'discord.js';
// Available for evals
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import ClientUtils from './ClientUtils.js';
import Metrics from './Metrics.js';
// import ApiClientUtil from './ApiClientUtil.js';
import SlashCommandManager from './rest/SlashCommandManager.js';
import { Shard } from './shard/index.js';
import { existsSync } from 'node:fs';
import Util from '../utilities/Util.js';
// Placeholder
type GalacticAPI = {
init: () => Promise<void>
}
class Controller extends EventEmitter
{
// #shardingManager: ShardingManager;
#slashCommandManager: SlashCommandManager;
#logger: MasterLogger;
#metrics: Metrics;
#options: ControllerOptions;
#shardingOptions: ShardingOptions;
// #apiClientUtil: ApiClientUtil;
#shards: Collection<number, Shard>;
#version: string;
#readyAt: number | null;
#built: boolean;
#api?: GalacticAPI;
constructor (options: ControllerOptions, version: string)
{
super();
// Sharding
const respawn = process.env.NODE_ENV !== 'development';
const clientPath = path.join(options.rootDir, 'client/DiscordClient.js');
if (!existsSync(clientPath))
throw new Error(`Client path does not seem to exist: ${clientPath}`);
this.#options = options;
const { shardList, totalShards } = Controller.parseShardOptions(options.shardOptions);
options.discord.rootDir = options.rootDir;
options.discord.logger = options.logger;
options.discord.storage = options.storage;
options.discord.version = version;
this.#shardingOptions = {
path: clientPath,
totalShards,
shardList,
respawn,
shardArgs: [],
execArgv: [],
token: process.env.DISCORD_TOKEN,
clientOptions: options.discord,
};
// Other
this.#slashCommandManager = new SlashCommandManager(this);
this.#logger = new MasterLogger(options.logger);
this.#metrics = new Metrics(this);
// this.#apiClientUtil = new ApiClientUtil(this);
this.#version = version;
this.#readyAt = null;
this.#built = false;
this.#shards = new Collection();
// this.#shardingManager.on('message', this._handleMessage.bind(this));
process.on('SIGINT', this.shutdown.bind(this));
}
async build ()
{
const start = Date.now();
// const API = this._options.api.load ? await import('/Documents/My programs/GBot/api/index.js')
// .catch(() => this.logger.warn(`Error importing API files, continuing without`)) : null;
// let API = null;
// if (this.#options.api.load)
// API = await import('../../api/index.js').catch(() => this.#logger.warn(`Error importing API files, continuing without`));
// if (API) {
// // TODO: this needs to be fixed up
// this.#logger.info('Booting up API');
// const { default: APIManager } = API;
// this.#api = new APIManager(this, this.#options.api) as GalacticAPI;
// await this.#api.init();
// const now = Date.now();
// this.#logger.info(`API ready. Took ${now - start} ms`);
// start = now;
// }
this.#logger.status('Starting bot shards');
// await this.shardingManager.spawn().catch((error) => {
// this.#logger.error(`Fatal error during shard spawning:\n${error.stack || inspect(error)}`);
// // eslint-disable-next-line no-process-exit
// process.exit(); // Prevent a boot loop when shards die due to an error in the client
// });
const { totalShards, token } = this.#shardingOptions;
let shardCount = 0;
if (totalShards === 'auto')
{
if (!token)
throw new Error('Missing token');
shardCount = await Util.fetchRecommendedShards(token);
}
else
{
if (typeof shardCount !== 'number' || isNaN(shardCount))
throw new TypeError('Amount of shards must be a number.');
if (shardCount < 1)
throw new RangeError('Amount of shards must be at least one.');
if (!Number.isInteger(shardCount))
throw new TypeError('Amount of shards must be an integer.');
}
const promises = [];
for (let i = 0; i < shardCount; i++)
{
const shard = this.createShard(shardCount);
promises.push(shard.spawn());
}
await Promise.all(promises);
this.#logger.status(`Shards spawned, spawned ${this.#shards.size} shards. Took ${Date.now() - start} ms`);
this.#built = true;
this.#readyAt = Date.now();
}
async shutdown ()
{
this.logger.info('Received SIGINT, shutting down');
setTimeout(process.exit, 90_000);
const promises = this.shards
.filter(shard => shard.ready)
.map(shard => shard.awaitShutdown()
.then(() => shard.removeAllListeners()));
if (promises.length)
await Promise.all(promises);
this.logger.status('Shutdown complete, goodbye');
this.logger.close();
}
createShard (totalShards: number)
{
const ids = this.#shards.map(s => s.id);
const id = ids.length ? Math.max(...ids) + 1 : 0;
const { path: file, token, respawn, execArgv, shardArgs: args, clientOptions: discordOptions } = this.#shardingOptions;
if (!file)
throw new Error('File seems to be missing');
if (!discordOptions)
throw new Error('Missing discord options');
const shard = new Shard(this, id, {
file,
token,
respawn,
args,
execArgv,
totalShards,
clientOptions: discordOptions
});
this.#shards.set(shard.id, shard);
this.#logger.attach(shard);
this.#setListeners(shard);
return shard;
}
#setListeners (shard: Shard)
{
shard.on('death', () => this.#logger.info(`Shard ${shard.id} has died`));
shard.on('fatal', ({ error }) => this.#logger.warn(`Shard ${shard.id} has died fatally: ${inspect(error) ?? ''}`));
shard.on('shutdown', () => this.#logger.info(`Shard ${shard.id} is shutting down gracefully`));
shard.on('ready', () => this.#logger.info(`Shard ${shard.id} is ready`));
shard.on('disconnect', () => this.#logger.warn(`Shard ${shard.id} has disconnected`));
shard.on('processDisconnect', () => this.#logger.warn(`Process for ${shard.id} has disconnected`));
shard.on('spawn', () => this.#logger.info(`Shard ${shard.id} spawned`));
shard.on('error', (err) => this.#logger.error(`Shard ${shard.id} ran into an error:\n${err.stack}`));
shard.on('warn', (msg) => this.#logger.warn(`Warning from shard ${shard.id}: ${msg}`, { broadcast: true }));
shard.on('message', (msg) => this.#handleMessage(shard, msg));
}
#handleMessage (shard: Shard, message: IPCMessage)
{
if (message._logger)
return;
// this.logger.debug(`New message from ${shard ? `${message._api ? 'api-' : ''}shard ${shard.id}`: 'manager'}: ${inspect(message)}`);
if (message._mEval)
return this.eval(shard, { script: message._mEval, debug: message.debug || false });
if (message._commands)
return this.#slashCommandManager._handleMessage(message as CommandsDef);
if (message._api)
return this.apiRequest(shard, message);
}
apiRequest (shard: Shard, message: IPCMessage)
{
const { type } = message;
switch (type)
{
case 'stats':
this.#metrics.aggregateStatistics(shard, message);
break;
default:
// this.#apiClientUtil.handleMessage(shard, message);
}
}
/**
* @param {*} shard The shard from which the eval came and to which it will be returned
* @param {*} script The script to be executed
* @memberof Manager
* @private
*/
async eval (shard: Shard, { script, debug }: {script: string, debug: boolean})
{
this.#logger.info(`Incoming manager eval from shard ${shard.id}:\n${script}`);
let result = null,
error = null;
const response: IPCMessage = {
script, _mEvalResult: true
};
try
{
// eslint-disable-next-line no-eval
result = await eval(script);
response._result = result;
// if(typeof result !== 'string') result = inspect(result);
if (debug)
this.#logger.debug(`Eval result: ${inspect(result)}`);
}
catch (e)
{
const err = e as Error;
error = Util.makePlainError(err);
response._error = error;
}
return shard.send(response);
}
// eslint-disable-next-line @typescript-eslint/ban-types
broadcastEval (script: string | Function, options: BroadcastEvalOptions = {})
{
if (typeof script !== 'function')
return Promise.reject(new TypeError('[shardmanager] Provided eval must be a function.'));
return this._performOnShards('eval', [ `(${script})(this, ${JSON.stringify(options.context)})` ], options.shard);
}
fetchClientValues (prop: string, shard?: number)
{
return this._performOnShards('fetchClientValue', [ prop ], shard);
}
_performOnShards (method: ShardMethod, args: [string, object?], shard?: number): Promise<unknown>
{
if (this.#shards.size === 0)
return Promise.reject(new Error('No shards available.'));
if (!this.ready)
return Promise.reject(new Error('Controller not ready'));
if (typeof shard === 'number')
{
if (!this.#shards.has(shard))
Promise.reject(new Error('Shard not found.'));
const s = this.#shards.get(shard) as Shard;
if (method === 'eval')
return s.eval(...args);
else if (method === 'fetchClientValue')
return s.eval(args[0]);
}
const promises = [];
for (const sh of this.#shards.values())
{
if (method === 'eval')
promises.push(sh.eval(...args));
else if (method === 'fetchClientValue')
promises.push(sh.eval(args[0]));
}
return Promise.all(promises);
}
async respawnAll ({ shardDelay = 5000, respawnDelay = 500, timeout = 30000 } = {})
{
let s = 0;
for (const shard of this.#shards.values())
{
const promises: Promise<unknown>[] = [ shard.respawn({ delay: respawnDelay, timeout }) ];
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;
}
static parseShardOptions (options: ShardingOptions)
{
let shardList = options.shardList ?? 'auto';
if (shardList !== 'auto')
{
if (!Array.isArray(shardList))
throw new Error('ShardList must be an array.');
shardList = [ ...new Set(shardList) ];
if (shardList.length < 1)
throw new Error('ShardList must have at least one ID.');
if (shardList.some((shardId) => typeof shardId !== 'number' || isNaN(shardId) || !Number.isInteger(shardId) || shardId < 0))
throw new Error('ShardList must be an array of positive integers.');
}
const totalShards = options.totalShards || 'auto';
if (totalShards !== 'auto')
{
if (typeof totalShards !== 'number' || isNaN(totalShards))
throw new Error('TotalShards must be an integer.');
if (totalShards < 1)
throw new Error('TotalShards must be at least one.');
if (!Number.isInteger(totalShards))
throw new Error('TotalShards must be an integer.');
}
return { shardList, totalShards };
}
get version ()
{
return this.#version;
}
get ready ()
{
return this.#built;
}
get readyAt ()
{
return this.#readyAt || -1;
}
get totalShards ()
{
return this.#shardingOptions.totalShards as number;
}
get developerGuilds ()
{
return this.#options.discord.slashCommands?.developerGuilds;
}
get logger ()
{
return this.#logger;
}
get api ()
{
return this.#api;
}
get shards ()
{
return this.#shards.clone();
}
}
export default Controller;

View File

@ -342,28 +342,28 @@ class Shard extends EventEmitter
{
if (message)
{
if (message._ready)
if (message._ready)
{
this.#ready = true;
this.emit('ready');
return;
}
if (message._disconnect)
if (message._disconnect)
{
this.#ready = false;
this.emit('disconnect');
return;
}
if (message._reconnecting)
if (message._reconnecting)
{
this.#ready = false;
this.emit('reconnecting');
return;
}
if (message._sFetchProp)
if (message._sFetchProp)
{
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
this.#manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
@ -373,7 +373,7 @@ class Shard extends EventEmitter
return;
}
if (message._sEval)
if (message._sEval)
{
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
this.#manager._performOnShards('eval', [ message._sEval ], message._sEvalShard).then(
@ -383,7 +383,7 @@ class Shard extends EventEmitter
return;
}
if (message._sRespawnAll)
if (message._sRespawnAll)
{
const { shardDelay, respawnDelay, timeout } = message._sRespawnAll;
this.#manager.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() =>
@ -393,7 +393,16 @@ class Shard extends EventEmitter
return;
}
if (message._fatal)
if (message._shutdown)
{
const TO = setTimeout(() => this.#process?.kill('SIGKILL'), KillTO);
this.#process?.once('exit', () => clearTimeout(TO));
this.#ready = false;
this.emit('shutdown');
return;
}
if (message._fatal)
{
this.#process?.removeAllListeners();
this.#ready = false;

View File

@ -940,14 +940,14 @@ __metadata:
languageName: node
linkType: hard
"@navy.gif/logger@npm:^2.5.2":
version: 2.5.2
resolution: "@navy.gif/logger@npm:2.5.2"
"@navy.gif/logger@npm:^2.5.3":
version: 2.5.3
resolution: "@navy.gif/logger@npm:2.5.3"
dependencies:
"@navy.gif/discord-webhook": ^1.0.0
chalk: ^4.1.2
moment: ^2.29.4
checksum: b0cede9024333016a525d0a82b9c45ebad2ba1c9484fb2c1cf0743c2f5d8b83812b4d1f603837984c0a2b2742b8ae62a60787e90b242d8545282a60899eaa2ba
checksum: 0936998e000f55dbe573db27b25f82fc02c4f906abb6f933634622f99e11035bf6f5f37047421d55db19a54e67a6f8f59962c60353862954f629c9dd79ff846d
languageName: node
linkType: hard
@ -4087,7 +4087,7 @@ __metadata:
dependencies:
"@discordjs/collection": ^1.5.1
"@discordjs/rest": ^1.7.1
"@navy.gif/logger": ^2.5.2
"@navy.gif/logger": ^2.5.3
"@navy.gif/timestring": ^6.0.6
"@types/common-tags": ^1.8.1
"@types/humanize-duration": ^3.27.1