Compare commits
No commits in common. "d2a9348051cc138030b8f62138995801680358ee" and "46ad540f68869c2c1b946588ed20c166924e095f" have entirely different histories.
d2a9348051
...
46ad540f68
@ -3,7 +3,6 @@
|
|||||||
"shardOptions": {
|
"shardOptions": {
|
||||||
"respawn": false
|
"respawn": false
|
||||||
},
|
},
|
||||||
"shardCount": 1,
|
|
||||||
"serverOptions": {
|
"serverOptions": {
|
||||||
"serveFiles": "./files",
|
"serveFiles": "./files",
|
||||||
"callbackURL": "/api/login",
|
"callbackURL": "/api/login",
|
||||||
|
@ -38,8 +38,6 @@ class Controller extends EventEmitter {
|
|||||||
this.logger.error(ex.stack);
|
this.logger.error(ex.stack);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', this.#shutdown.bind(this));
|
|
||||||
|
|
||||||
this.on('built', () => {
|
this.on('built', () => {
|
||||||
this.logger.info(`API Controller built`);
|
this.logger.info(`API Controller built`);
|
||||||
this._built = true;
|
this._built = true;
|
||||||
@ -92,16 +90,6 @@ class Controller extends EventEmitter {
|
|||||||
shard.on('message', (msg) => this._handleMessage(shard, msg));
|
shard.on('message', (msg) => this._handleMessage(shard, msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
async #shutdown () {
|
|
||||||
this.logger.info('Received SIGINT, shutting down');
|
|
||||||
const promises = this.shards.map(shard => shard.awaitShutdown());
|
|
||||||
await Promise.all(promises);
|
|
||||||
this.logger.info(`Shutdown completed, goodbye`);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Controller;
|
module.exports = Controller;
|
@ -2,8 +2,7 @@ const ChildProcess = require('node:child_process');
|
|||||||
const { EventEmitter } = require('node:events');
|
const { EventEmitter } = require('node:events');
|
||||||
|
|
||||||
const { Util } = require('../util');
|
const { Util } = require('../util');
|
||||||
// Give subprocess 90s to shut down before being forcibly killed
|
|
||||||
const KillTO = 90 * 1000;
|
|
||||||
class Shard extends EventEmitter {
|
class Shard extends EventEmitter {
|
||||||
|
|
||||||
constructor (controller, id, options = {}) {
|
constructor (controller, id, options = {}) {
|
||||||
@ -32,8 +31,6 @@ class Shard extends EventEmitter {
|
|||||||
// Set in the spawn method
|
// Set in the spawn method
|
||||||
this.spawnedAt = null;
|
this.spawnedAt = null;
|
||||||
|
|
||||||
this._awaitingShutdown = null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async spawn (waitForReady = false) {
|
async spawn (waitForReady = false) {
|
||||||
@ -88,7 +85,7 @@ class Shard extends EventEmitter {
|
|||||||
const to = setTimeout(() => {
|
const to = setTimeout(() => {
|
||||||
this.process.kill();
|
this.process.kill();
|
||||||
resolve();
|
resolve();
|
||||||
}, KillTO);
|
}, 5000);
|
||||||
// Gracefully handle exit
|
// Gracefully handle exit
|
||||||
this.process.once('exit', (code, signal) => {
|
this.process.once('exit', (code, signal) => {
|
||||||
clearTimeout(to);
|
clearTimeout(to);
|
||||||
@ -121,13 +118,6 @@ class Shard extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitShutdown () {
|
|
||||||
this._respawn = false;
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this._awaitingShutdown = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMessage (message) {
|
_handleMessage (message) {
|
||||||
if (message) {
|
if (message) {
|
||||||
if (message._ready) {
|
if (message._ready) {
|
||||||
@ -135,10 +125,6 @@ class Shard extends EventEmitter {
|
|||||||
this.emit('ready');
|
this.emit('ready');
|
||||||
return;
|
return;
|
||||||
} else if (message._shutdown) {
|
} else if (message._shutdown) {
|
||||||
setTimeout(() => {
|
|
||||||
if (this.process)
|
|
||||||
this.process.kill('SIGKILL');
|
|
||||||
}, KillTO);
|
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.emit('shutdown');
|
this.emit('shutdown');
|
||||||
return;
|
return;
|
||||||
@ -164,8 +150,6 @@ class Shard extends EventEmitter {
|
|||||||
this.process.removeAllListeners();
|
this.process.removeAllListeners();
|
||||||
|
|
||||||
this.emit('death');
|
this.emit('death');
|
||||||
if (this._awaitingShutdown)
|
|
||||||
this._awaitingShutdown();
|
|
||||||
|
|
||||||
if (code !== 0)
|
if (code !== 0)
|
||||||
this.crashes.push(Date.now() - this.spawnedAt);
|
this.crashes.push(Date.now() - this.spawnedAt);
|
||||||
|
@ -155,7 +155,6 @@ class Server extends EventEmitter {
|
|||||||
this.app.use(this.#ready.bind(this)); // denies requests before the server is ready
|
this.app.use(this.#ready.bind(this)); // denies requests before the server is ready
|
||||||
|
|
||||||
process.on('message', this._handleMessage.bind(this));
|
process.on('message', this._handleMessage.bind(this));
|
||||||
process.on('SIGINT', this.shutdown.bind(this));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,19 +209,17 @@ class Server extends EventEmitter {
|
|||||||
|
|
||||||
async shutdown () {
|
async shutdown () {
|
||||||
this.logger.info(`Received shutdown command, initiating graceful shutdown`);
|
this.logger.info(`Received shutdown command, initiating graceful shutdown`);
|
||||||
// Tells the manager the shard is shutting down, will set a timeout to kill the shard if it hangs
|
|
||||||
process.send({ _shutdown: true });
|
process.send({ _shutdown: true });
|
||||||
|
|
||||||
this._ready = false;
|
this._ready = false; // stops any new connections
|
||||||
this.server.close(async () => {
|
|
||||||
await this.mongodb.close();
|
|
||||||
await this.mariadb.close();
|
|
||||||
this.logger.status('DB shutdowns complete.');
|
|
||||||
|
|
||||||
this.logger.status('Shutdown complete. Goodbye');
|
await this.mongodb.close();
|
||||||
// eslint-disable-next-line no-process-exit
|
await this.mariadb.close();
|
||||||
process.exit();
|
this.logger.status('DB shutdowns complete.');
|
||||||
});
|
|
||||||
|
this.logger.status('Shutdown complete. Goodbye');
|
||||||
|
// eslint-disable-next-line no-process-exit
|
||||||
|
process.exit();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@ const mysql = require('mysql');
|
|||||||
const { inspect } = require('node:util');
|
const { inspect } = require('node:util');
|
||||||
const { Util } = require('../../util');
|
const { Util } = require('../../util');
|
||||||
|
|
||||||
const SAFE_TO_RETRY = [ 'ER_LOCK_DEADLOCK' ];
|
|
||||||
|
|
||||||
class MariaDB {
|
class MariaDB {
|
||||||
|
|
||||||
constructor (server, config) {
|
constructor (server, config) {
|
||||||
@ -41,6 +39,7 @@ class MariaDB {
|
|||||||
this.pool = mysql.createPoolCluster(this.config.options.cluster);
|
this.pool = mysql.createPoolCluster(this.config.options.cluster);
|
||||||
for (const creds of this._credentials)
|
for (const creds of this._credentials)
|
||||||
this.pool.add({ ...this.config.options.client, ...creds });
|
this.pool.add({ ...this.config.options.client, ...creds });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.pool = mysql.createPool({ ...this.config.options.client, ...this._credentials[0] });
|
this.pool = mysql.createPool({ ...this.config.options.client, ...this._credentials[0] });
|
||||||
}
|
}
|
||||||
@ -107,56 +106,30 @@ class MariaDB {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnection () {
|
query (query, values) {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pool.getConnection((err, conn) => {
|
|
||||||
|
if (!this.ready)
|
||||||
|
return reject(new Error('MariaDB not ready'));
|
||||||
|
|
||||||
|
let batch = false;
|
||||||
|
if (values && typeof values.some === 'function')
|
||||||
|
batch = values.some(val => val instanceof Array);
|
||||||
|
this.logger.debug(`Incoming query (batch: ${batch})\n${query}\n${inspect(values)}`);
|
||||||
|
|
||||||
|
// If connected to a cluster, pick one of the node connection pools
|
||||||
|
// By default the config is set to use round robin for picking a pool when connected to a cluster
|
||||||
|
const pool = this._cluster ? this.pool.of('*') : this.pool;
|
||||||
|
|
||||||
|
return pool.query(query, values, (err, results, fields) => {
|
||||||
if (err)
|
if (err)
|
||||||
return reject(err);
|
return reject(err);
|
||||||
resolve(conn);
|
if (results)
|
||||||
|
return resolve(results);
|
||||||
|
resolve(fields);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retry certain queries that are safe to retry, e.g. deadlock
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* */
|
|
||||||
async #_query (query, values, attempts = 0) {
|
|
||||||
const connection = await this.getConnection();
|
|
||||||
try {
|
|
||||||
const result = await new Promise((resolve, reject) => {
|
|
||||||
const q = connection.query(query, values, (err, results, fields) => {
|
|
||||||
if (err)
|
|
||||||
reject(err);
|
|
||||||
else if (results)
|
|
||||||
resolve(results);
|
|
||||||
else
|
|
||||||
resolve(fields);
|
|
||||||
connection.release();
|
|
||||||
});
|
|
||||||
this.logger.debug(`Constructed query: ${q.sql}`);
|
|
||||||
});
|
|
||||||
return Promise.resolve(result);
|
|
||||||
} catch (err) {
|
|
||||||
// Retry safe errors
|
|
||||||
if (SAFE_TO_RETRY.includes(err.code) && attempts < 5) //
|
|
||||||
return this.#_query(query, values, ++attempts);
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async query (query, values) {
|
|
||||||
|
|
||||||
if (!this.ready)
|
|
||||||
return Promise.reject(new Error('MariaDB not ready'));
|
|
||||||
|
|
||||||
let batch = false;
|
|
||||||
if (values && typeof values.some === 'function')
|
|
||||||
batch = values.some(val => val instanceof Array);
|
|
||||||
this.logger.debug(`Incoming query (batch: ${batch})\n${query}\n${inspect(values)}`);
|
|
||||||
|
|
||||||
return this.#_query(query, values);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ class Util {
|
|||||||
return Math.floor(Date.now() / 1000);
|
return Math.floor(Date.now() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
static wait (timeMs) {
|
static wait (time) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(resolve, timeMs);
|
setTimeout(resolve, time);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
start.json
14
start.json
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"name": "framework",
|
|
||||||
"script": "index.js",
|
|
||||||
"interpreter_args": "",
|
|
||||||
"error_file": "/dev/null",
|
|
||||||
"out_file": "/dev/null",
|
|
||||||
"kill_timeout": 90000,
|
|
||||||
"max_restarts": 5,
|
|
||||||
"restart_delay": 5000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user