UNPKG

watchdog

Version:

An Timer used to Detect and Recover from Malfunctions

204 lines 6.83 kB
// https://github.com/andrew-filonenko/ya-watchdog // https://en.wikipedia.org/wiki/Watchdog_timer import { EventEmitter } from 'events'; import { Brolog } from 'brolog'; export const log = new Brolog(); export const VERSION = require('../package.json').version; export class Watchdog extends EventEmitter { /** * A Timer used to detect and recover from malfunctions * * @class Watchdog * @param {number} [defaultTimeout=60 * 1000] * @param {string} [name='Bark'] * @example * const TIMEOUT = 1 * 1000 // 1 second * const dog = new watchdog(TIMEOUT) * * const food = { data: 'delicious' } * * dog.on('reset', () => console.log('reset-ed')) * dog.on('feed', () => console.log('feed-ed')) * * dog.feed(food) * // Output: feed-ed * * setTimeout(function() { * dog.sleep() * console.log('dog sleep-ed. Demo over.') * }, TIMEOUT + 1) * // Output: reset-ed. * // Output: dog sleep-ed. Demo over. * */ constructor(defaultTimeout = 60 * 1000, name = 'Bark') { super(); this.defaultTimeout = defaultTimeout; this.name = name; log.verbose('Watchdog', '<%s>: constructor(name=%s, defaultTimeout=%d)', name, name, defaultTimeout); } version() { return VERSION; } /** * @desc Watchdog Class Event Type * @typedef WatchdogEvent * @property { string } feed - Emit when feed the dog. * @property { string } reset - Emit when timeout and reset. * @property { string } sleep - Emit when timer is cleared out. */ /** * @desc Watchdog Class Event Function * @typedef WatchdogListener * @property { Function } - (food: WatchdogFood<T, D>, left: number) => void */ /** * @listens Watchdog * @param { WatchdogEvent } event * @param { WatchdogListener<T, D> } listener * @returns { this } * * @example <caption>Event:reset </caption> * dog.on('reset', () => console.log('reset-ed')) * @example <caption>Event:feed </caption> * dog.on('feed', () => console.log('feed-ed')) * @example <caption>Event:sleep </caption> * dog.on('sleep', () => console.log('sleep-ed')) * */ on(event, listener) { log.verbose('Watchdog', '<%s> on(%s, listener) registered.', this.name, event); super.on(event, listener); return this; } startTimer(timeout) { log.verbose('Watchdog', '<%s> startTimer()', this.name); if (this.timer) { throw new Error('timer already exist!'); } this.timer = setTimeout(() => { log.verbose('Watchdog', '<%s> startTimer() setTimeout() after %d', this.name, timeout); this.timer = undefined; // sleep after reset this.emit('reset', this.lastFood, this.lastFood && this.lastFood.timeout || this.defaultTimeout); }, timeout); this.timer.unref(); // should not block node quit return; } stopTimer(sleep = false) { log.verbose('Watchdog', '<%s> stopTimer()', this.name); if (typeof this.timer === 'undefined') { // first time log.verbose('Watchdog', '<%s> stopTimer() first run(or after sleep)', this.name); return; } if (this.timer) { clearTimeout(this.timer); this.timer = null; } else if (!sleep) { throw new Error('timer is already stoped!'); } } /** * Get the left time * @returns {number} */ left() { let left; if (typeof this.lastFeed !== 'undefined' && Number.isInteger(this.lastFeed)) { // console.log('lastFeed=', this.lastFeed) // console.log('timeout=', this.lastFood.timeout) // console.log('Date.now()=', Date.now()) left = this.lastFeed + this.defaultTimeout - Date.now(); log.verbose('Watchdog', '<%s> timerLeft() = %d', this.name, left); } else { left = 0; log.verbose('Watchdog', '<%s> timerLeft() first feed, left=%s', this.name, left); } return left; } /** * Dog Feed content * * @typedef WatchdogFood * @property {D} data - feed content. * @property {number} timeout - option, set timeout. * @property {T} type - option. */ /** * feed the dog * @param {WatchdogFood} food * @returns {number} * @example * const food = { * data: 'delicious', * timeout: 1 * 1000, * } * const dog = new Watchdog() * dog.feed(food) */ feed(food) { // JSON.stringify, avoid TypeError: Converting circular structure to JSON // https://stackoverflow.com/a/11616993/1123955 function replacerFactory() { // Note: cache should not be re-used by repeated calls to JSON.stringify. const cache = []; return function (_, value) { if (typeof value === 'object' && value !== null) { if (cache.indexOf(value) !== -1) { // Circular reference found, discard key return; } // Store value in our collection cache.push(value); } return value; }; } log.verbose('Watchdog', '<%s> feed(%s)', this.name, JSON.stringify(food, replacerFactory())); if (typeof food !== 'object') { /** * weak typing compitable: * if user call watchdog.feed('string'), we need to pre-processs the food to a object. * or we will meet a exception: we can not set property on string type. */ food = { data: food, }; } if (!food.timeout) { food.timeout = this.defaultTimeout; } const left = this.left(); this.stopTimer(); this.startTimer(food.timeout); this.lastFeed = Date.now(); this.lastFood = food; this.emit('feed', food, left); return left; } /** * Clear timer. * @example * const dog = new Watchdog() * dog.sleep() */ sleep() { log.verbose('Watchdog', '<%s> sleep()', this.name); this.stopTimer(true); this.timer = undefined; this.emit('sleep', this.lastFood, this.left()); } /** * */ unref() { log.verbose('Watchdog', '<%s> unref()', this.name); if (this.timer) { this.timer.unref(); } } } export default Watchdog; //# sourceMappingURL=watchdog.js.map