From a3e67936329e32785aea980c651c4c88cc46f759 Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Mon, 1 Apr 2024 14:01:30 +0300 Subject: [PATCH] Various fixes Hopefully prevent danging child processes Reduce unnecessary db calls in moderation manager --- src/client/DiscordClient.ts | 24 ++--- .../components/managers/CallbackManager.ts | 4 +- .../components/managers/ModerationManager.ts | 6 +- src/middleware/Controller.ts | 87 ++++++++----------- src/middleware/Metrics.ts | 2 +- src/middleware/{shard => }/Shard.ts | 27 +++--- .../{rest => }/SlashCommandManager.ts | 4 +- src/middleware/shard/index.ts | 6 -- 8 files changed, 74 insertions(+), 86 deletions(-) rename src/middleware/{shard => }/Shard.ts (94%) rename src/middleware/{rest => }/SlashCommandManager.ts (97%) delete mode 100644 src/middleware/shard/index.ts diff --git a/src/client/DiscordClient.ts b/src/client/DiscordClient.ts index 57df97e..e9509e8 100644 --- a/src/client/DiscordClient.ts +++ b/src/client/DiscordClient.ts @@ -210,14 +210,6 @@ class DiscordClient extends Client this.emit('invalidRequestWarning', ...args); }); - // this.once('ready', () => { - // this._setActivity(); - - // setInterval(() => { - // this._setActivity(); - // }, 1800000); // I think this is 30 minutes. I could be wrong. - // }); - this.#loadEevents(); // process.on('uncaughtException', (err) => { @@ -230,7 +222,13 @@ class DiscordClient extends Client }); process.on('message', this.#handleMessage.bind(this)); - process.on('SIGINT', () => this.shutdown()); + process.on('SIGINT', () => this.logger.info('Received SIGINT')); + process.on('SIGTERM', () => this.logger.info('Received SIGTERM')); + process.on('disconnect', () => + { + this.logger.info('Process disconnected from parent for some reason, exiting'); + this.shutdown(1); + }); } async build () @@ -271,7 +269,7 @@ class DiscordClient extends Client this.#activityInterval = setInterval(() => { this.setActivity(); - }, Util.random(5, 10) * 60 * 60 * 1000); + }, Util.random(5, 10) * 60 * 1000); } this.#built = true; @@ -296,6 +294,8 @@ class DiscordClient extends Client // Handle misc. messages. if (message._mEvalResult) this.evalResult(message); + if (message._shutdown) + this.shutdown(); } // eslint-disable-next-line @typescript-eslint/ban-types @@ -397,6 +397,10 @@ class DiscordClient extends Client this.user?.setActivity(`${userCount} users`, { type: ActivityType.Listening }); }, async () => + { + this.user?.setActivity('out for troublemakers', { type: ActivityType.Watching }); + }, + async () => { this.user?.setActivity('for /help', { type: ActivityType.Listening }); } diff --git a/src/client/components/managers/CallbackManager.ts b/src/client/components/managers/CallbackManager.ts index 9485067..f167064 100644 --- a/src/client/components/managers/CallbackManager.ts +++ b/src/client/components/managers/CallbackManager.ts @@ -185,9 +185,9 @@ class CallbackManager implements Initialisable * * @async * @param {string} id - * @returns {*} + * @returns {Promise} */ - async removeCallback (id: string) + async removeCallback (id: string): Promise { await this.storage.deleteOne({ _id: id }); const timeout = this.#timeouts.get(id); diff --git a/src/client/components/managers/ModerationManager.ts b/src/client/components/managers/ModerationManager.ts index 533a936..9db7c12 100644 --- a/src/client/components/managers/ModerationManager.ts +++ b/src/client/components/managers/ModerationManager.ts @@ -591,9 +591,9 @@ class ModerationManager implements Initialisable, CallbackClient return responses; } - async handleCallback (id: string) + async handleCallback (_id: string, infraction: InfractionJSON) { - const infraction = await this.#client.mongodb.infractions.findOne({ id }); + // const infraction = await this.#client.mongodb.infractions.findOne({ id }); if (!infraction) return; this.#logger.debug(`Infraction callback: ${infraction.id} (${infraction.type})`); @@ -667,7 +667,7 @@ class ModerationManager implements Initialisable, CallbackClient if (expiresAt - currentDate <= 0) { this.#logger.debug(`Expired infraction:\n${inspect(infraction)}`); - return this.handleCallback(infraction.id); + return this.handleCallback(infraction.id, infraction); } this.#logger.debug(`Creating infraction callback for ${infraction.id} (${infraction.type}), expiring in ${Util.humanise(duration / 1000)}`); diff --git a/src/middleware/Controller.ts b/src/middleware/Controller.ts index 9828da4..849c3ce 100644 --- a/src/middleware/Controller.ts +++ b/src/middleware/Controller.ts @@ -1,27 +1,47 @@ -import { EventEmitter } from 'node:events'; -import { inspect } from 'node:util'; +import { + EventEmitter +} from 'node:events'; + +import { + inspect +} from 'node:util'; + +import { + existsSync +} from 'node:fs'; + 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 { + CommandsDef, + IPCMessage +} from '../../@types/Shared.js'; -import { MasterLogger } from '@navy.gif/logger'; -import { Collection } from 'discord.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 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 SlashCommandManager from './SlashCommandManager.js'; import Util from '../utilities/Util.js'; - -// Placeholder -type GalacticAPI = { - init: () => Promise -} +import Shard from './Shard.js'; class Controller extends EventEmitter { @@ -39,7 +59,6 @@ class Controller extends EventEmitter #readyAt: number | null; #built: boolean; - #api?: GalacticAPI; clientUtils: typeof ClientUtils; constructor (options: ControllerOptions, version: string) @@ -92,28 +111,7 @@ class Controller extends EventEmitter 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) { - // 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; @@ -133,7 +131,6 @@ class Controller extends EventEmitter throw new TypeError('Amount of shards must be an integer.'); } - // const promises = []; const retry: Shard[] = []; for (let i = 0; i < shardCount; i++) { @@ -148,9 +145,9 @@ class Controller extends EventEmitter this.logger.info('Retrying shard', i); retry.push(shard); } - // promises.push(); } + // Retry failed spawns for (const shard of retry) { try @@ -163,8 +160,6 @@ class Controller extends EventEmitter } } - // await Promise.all(promises); - this.#logger.status(`Shards spawned, spawned ${this.#shards.size} shards. Took ${Date.now() - start} ms`); this.#built = true; @@ -173,12 +168,11 @@ class Controller extends EventEmitter async shutdown () { - this.logger.info('Received SIGINT, shutting down'); + this.logger.info('Received SIGINT or SIGTERM, shutting down'); setTimeout(process.exit, 90_000); const promises = this.shards .filter(shard => shard.ready) - .map(shard => shard.awaitShutdown() - .then(() => shard.removeAllListeners())); + .map(shard => shard.kill()); if (promises.length) await Promise.all(promises); this.logger.status('Shutdown complete, goodbye'); @@ -406,11 +400,6 @@ class Controller extends EventEmitter return this.#logger; } - get api () - { - return this.#api; - } - get shards () { return this.#shards.clone(); diff --git a/src/middleware/Metrics.ts b/src/middleware/Metrics.ts index cf3e3e6..90d1596 100644 --- a/src/middleware/Metrics.ts +++ b/src/middleware/Metrics.ts @@ -1,6 +1,6 @@ import BaseClient from './Controller.js'; import os from 'node:os'; -import Shard from './shard/Shard.js'; +import Shard from './Shard.js'; import { IPCMessage } from '../../@types/Shared.js'; import djs, { GuildMessageManager } from 'discord.js'; import DiscordClient from '../client/DiscordClient.js'; diff --git a/src/middleware/shard/Shard.ts b/src/middleware/Shard.ts similarity index 94% rename from src/middleware/shard/Shard.ts rename to src/middleware/Shard.ts index 6aa365e..ee1343a 100644 --- a/src/middleware/shard/Shard.ts +++ b/src/middleware/Shard.ts @@ -1,13 +1,14 @@ import EventEmitter from 'node:events'; import path from 'node:path'; -import { makePlainError, makeError, MakeErrorOptions } from 'discord.js'; -import { Util } from '../../utilities/index.js'; import childProcess, { ChildProcess } from 'node:child_process'; +import { makePlainError, makeError, MakeErrorOptions } from 'discord.js'; -import { EnvObject, IPCMessage } from '../../../@types/Shared.js'; -import Controller from '../Controller.js'; -import { ShardOptions } from '../../../@types/Shard.js'; -import { ClientOptions } from '../../../@types/Client.js'; +import Controller from './Controller.js'; +import { Util } from '../utilities/index.js'; + +import { ShardOptions } from '../../@types/Shard.js'; +import { ClientOptions } from '../../@types/Client.js'; +import { EnvObject, IPCMessage } from '../../@types/Shared.js'; const KillTO = 90 * 1000; @@ -16,8 +17,8 @@ class Shard extends EventEmitter [key: string]: unknown; #id: number; - #manager: Controller; - #env: EnvObject; // { [key: string]: string | boolean | number }; + #controller: Controller; + #env: EnvObject; #ready: boolean; #clientOptions: ClientOptions; @@ -38,10 +39,10 @@ class Shard extends EventEmitter #crashes: number[]; #fatal: boolean; - constructor (manager: Controller, id: number, options: ShardOptions) + constructor (controller: Controller, id: number, options: ShardOptions) { super(); - this.#manager = manager; + this.#controller = controller; this.#id = id; this.#args = options.args ?? []; @@ -370,7 +371,7 @@ class Shard extends EventEmitter if (message._sFetchProp) { const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard }; - this.#manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then( + this.#controller.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then( (results: unknown) => this.send({ ...resp, _result: results }), (error: Error) => this.send({ ...resp, _error: makePlainError(error) }) ); @@ -380,7 +381,7 @@ class Shard extends EventEmitter if (message._sEval) { const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard }; - this.#manager._performOnShards('eval', [ message._sEval ], message._sEvalShard).then( + this.#controller._performOnShards('eval', [ message._sEval ], message._sEvalShard).then( (results: unknown) => this.send({ ...resp, _result: results }), (error: Error) => this.send({ ...resp, _error: makePlainError(error) }) ); @@ -390,7 +391,7 @@ class Shard extends EventEmitter if (message._sRespawnAll) { const { shardDelay, respawnDelay, timeout } = message._sRespawnAll; - this.#manager.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() => + this.#controller.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() => { // }); diff --git a/src/middleware/rest/SlashCommandManager.ts b/src/middleware/SlashCommandManager.ts similarity index 97% rename from src/middleware/rest/SlashCommandManager.ts rename to src/middleware/SlashCommandManager.ts index 3ec51b1..9d0e041 100644 --- a/src/middleware/rest/SlashCommandManager.ts +++ b/src/middleware/SlashCommandManager.ts @@ -5,8 +5,8 @@ import fs from 'node:fs'; import path from 'node:path'; import { inspect } from 'node:util'; -import BaseClient from '../Controller.js'; -import { Command, CommandOption, CommandsDef } from '../../../@types/Shared.js'; +import BaseClient from './Controller.js'; +import { Command, CommandOption, CommandsDef } from '../../@types/Shared.js'; type CommandHashTable = { global: string | null, diff --git a/src/middleware/shard/index.ts b/src/middleware/shard/index.ts deleted file mode 100644 index e7f1f8e..0000000 --- a/src/middleware/shard/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Shard from './Shard.js'; -// import ShardingManager from './ShardingManager.js'; -export { - Shard, - // ShardingManager -}; \ No newline at end of file