UNPKG

state-switch

Version:

State Switch is a Change Monitor/Guarder for Async Actions.

194 lines 9.49 kB
/** * Licenst: Apache-2.0 * https://github.com/huan/state-switch */ import { getLoggable } from 'brolog'; import { timeoutPromise } from 'gerror'; import { ServiceableAbstract, } from '../interfaces.js'; import { StateSwitch } from '../state-switch.js'; import { BusyIndicator } from '../busy-indicator.js'; import { VERSION } from '../version.js'; /** * Wait from unknown state */ const TIMEOUT_SECONDS = 5; const RESET_TIMEOUT_SECONDS = TIMEOUT_SECONDS * 3; const serviceCtlMixin = (serviceCtlName = 'ServiceCtl', options) => (superClass) => { class ServiceCtlMixin extends superClass { static VERSION = VERSION; state; __serviceCtlResettingIndicator; __serviceCtlLogger; constructor(...args) { super(...args); this.__serviceCtlLogger = getLoggable(options?.log); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'constructor()'); this.state = new StateSwitch(serviceCtlName, options); this.__serviceCtlResettingIndicator = new BusyIndicator(serviceCtlName + 'Reset', options); } async start() { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start()'); if (this.state.active()) { this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'start() found that is starting/statred ...'); await this.state.stable('active'); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'start() found that is starting/statred ... done'); return; } if (this.state.inactive() === 'pending') { this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'start() found that is stopping, waiting stable ... (max %s seconds)', TIMEOUT_SECONDS); try { await timeoutPromise(this.state.stable('inactive'), TIMEOUT_SECONDS * 1000); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'start() found that is stopping, waiting stable ... done'); } catch (e) { this.emit('error', e); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'start() found that is stopping, waiting stable ... timeout'); } } this.state.active('pending'); try { /** * Parent start() */ if (typeof super.start === 'function') { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start() super.start() ...'); await super.start(); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start() super.start() ... done'); } /** * Child onStart() */ this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start() this.onStart() ...'); await this.onStart(); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start() this.onStart() ... done'); /** * the service has been successfully started */ this.state.active(true); } catch (e) { this.emit('error', e); await this.stop(); throw e; } this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'start() ... done'); } async stop() { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop()'); /** * Already in inactive/stop state: return directly */ if (this.state.inactive()) { this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is stopping/stopped, wait stable ...'); await this.state.stable('inactive'); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is stopping/stopped, wait stable ... done'); return; } /** * activing/starting: wait it to be finished first (with timeout) */ if (this.state.active() === 'pending') { this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is starting...'); try { this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is starting, waiting stable ... (max %s seconds)', TIMEOUT_SECONDS); await timeoutPromise(this.state.stable('active'), TIMEOUT_SECONDS * 1000); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is starting, waiting stable ... done'); } catch (e) { this.emit('error', e); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'stop() found that is starting, waiting stable ... timeout'); } } this.state.inactive('pending'); /** * Child onStop() */ try { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop() this.onStop() ...'); await this.onStop(); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop() this.onStop() ... done'); } catch (e) { this.emit('error', e); } /** * Parent stop() */ if (typeof super.stop === 'function') { try { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop() super.stop() ...'); await super.stop(); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop() super.stop() ... done'); } catch (e) { this.emit('error', e); } } /** * no matter whether the `try {...}` code success or not * set the service state to off(stopped) state */ this.state.inactive(true); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'stop() ... done'); } async reset() { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset()'); /** * Do not reset again if it's already resetting */ if (this.__serviceCtlResettingIndicator.busy()) { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() `resetBusy` is `busy`, wait `idle()` (max %d seconds) ...', RESET_TIMEOUT_SECONDS); try { await timeoutPromise(this.__serviceCtlResettingIndicator.idle(), RESET_TIMEOUT_SECONDS * 1000, () => new Error('wait resetting timeout')); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() `resetBusy` is `busy`, wait `idle()` (max %d seconds) ... done', RESET_TIMEOUT_SECONDS); return; } catch (e) { this.emit('error', e); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() `resetBusy` is `busy`, wait `idle()` (max %d seconds) ... timeout'); } } /** * Do not start Service if the stable state is OFF */ if (this.state.inactive()) { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() `state` is `off`, do nothing'); return; } this.__serviceCtlResettingIndicator.busy(true); /** * If the Service is starting/stopping, wait for it * The state will be `'active'` after await `stable()` */ try { this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() wait state ready() ...'); await timeoutPromise(this.state.stable(), 3 * TIMEOUT_SECONDS * 1000, () => new Error('state.ready() timeout')); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() wait state ready() ... done'); } catch (e) { this.emit('error', e); this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() wait state ready() ... timeout'); } /** * Do the stop() & start() job */ try { await this.stop(); await this.start(); } catch (e) { this.emit('error', e); this.__serviceCtlLogger.warn(`ServiceCtl<${serviceCtlName}>`, 'reset() ... rejection: %s', e.message); } finally { this.__serviceCtlResettingIndicator.busy(false); } this.__serviceCtlLogger.verbose(`ServiceCtl<${serviceCtlName}>`, 'reset() ... done'); } } return ServiceCtlMixin; }; class ServiceCtl extends serviceCtlMixin()(ServiceableAbstract) { } export { ServiceCtl, serviceCtlMixin, }; //# sourceMappingURL=service-ctl.js.map