import { Cluster } from '../Core/Cluster'; import { ClusterManager } from '../Core/ClusterManager'; import { messageType } from '../types/shared'; export type keepAliveOptions = { /** Default interval is 20000 */ interval?: number; /** Default maxMissedHeartbeats is 5 */ maxMissedHeartbeats?: number; }; export class HeartbeatManager { options: keepAliveOptions; clusters: Map; manager: null | ClusterManager; name: 'heartbeat'; constructor(options?: keepAliveOptions) { if (!options) options = {}; this.options = options; if (!this.options.interval) this.options.interval = 20000; if (!this.options.maxMissedHeartbeats) this.options.maxMissedHeartbeats = 5; this.clusters = new Map(); this.manager = null; this.name = 'heartbeat'; } public build(manager: ClusterManager) { manager[this.name] = this; this.manager = manager; this.start(); } public start() { this.manager?.on('clusterReady', (cluster: Cluster) => { if (this.clusters.has(cluster.id)) this.clusters.get(cluster.id)?.stop(); this.clusters.set(cluster.id, new Heartbeat(this, cluster, this.options as Required)); }); } public stop(cluster: Cluster, reason: string) { if (!this.clusters.has(cluster.id)) return; this.clusters.get(cluster.id)?.stop(); this.manager?._debug(`[Heartbeat_MISSING] ${reason}`, cluster.id); if (cluster.restarts.current < cluster.restarts.max) { cluster.respawn(this.manager?.spawnOptions); this.manager?._debug('[Heartbeat_MISSING] Attempted Respawn', cluster.id); } else { this.manager?._debug('[Heartbeat_MISSING] Respawn Rejected | Max Restarts of Cluster Reached', cluster.id); } } public ack(id: number, date: number) { if (!this.clusters.has(id)) return; this.clusters.get(id)?.ack(date); } } export class Heartbeat { manager: HeartbeatManager; options: Required; interval?: NodeJS.Timeout; heartbeats: Map; instance: Cluster; constructor(manager: HeartbeatManager, instance: Cluster, options: Required) { this.manager = manager; this.options = options; this.interval = undefined; this.heartbeats = new Map(); this.instance = instance; this.start(); } public ack(date: number) { return this.heartbeats.delete(date); } public start() { return (this.interval = setInterval(() => { const start = Date.now(); this.heartbeats.set(start, true); this.instance.send({ _type: messageType.HEARTBEAT, date: start })?.catch(() => null); if (this.heartbeats.size > this.options.maxMissedHeartbeats) { this.manager.stop(this.instance, `Missed ${this.heartbeats.size} Heartbeat Acks | Attempting Respawn`); } }, this.options.interval)); } public stop() { this.heartbeats.clear(); clearInterval(this.interval); } public resume() { this.start(); } }