UNPKG

node-opcua-utils

Version:

pure nodejs OPCUA SDK - module utils

190 lines (158 loc) 6.13 kB
/** * @module node-opcua-utils */ import { EventEmitter } from "events"; import { assert } from "node-opcua-assert"; import { get_clock_tick } from "./get_clock_tick"; type ArbitraryClockTick = number; // in millisecond type DurationInMillisecond = number; export interface IWatchdogData2 { key: number; subscriber: ISubscriber; timeout: DurationInMillisecond; lastSeen: ArbitraryClockTick; visitCount: number; } export interface ISubscriber { _watchDog?: WatchDog; _watchDogData?: IWatchdogData2; watchdogReset: () => void; keepAlive?: () => void; onClientSeen?: () => void; } function hasExpired(watchDogData: IWatchdogData2, currentTime: ArbitraryClockTick) { const elapsedTime = currentTime - watchDogData.lastSeen; return elapsedTime > watchDogData.timeout; } function keepAliveFunc(this: ISubscriber) { assert(this._watchDog instanceof WatchDog); // istanbul ignore next if (!this._watchDogData || !this._watchDog) { throw new Error("Internal error"); } assert(typeof this._watchDogData.key === "number"); this._watchDogData.lastSeen = this._watchDog.getCurrentSystemTick(); if (this.onClientSeen) { this.onClientSeen(); } } export class WatchDog extends EventEmitter { static lastSeenToDuration(lastSeen: number): number { return get_clock_tick() - lastSeen; } static emptyKeepAlive = (): void => { /* */ }; /** * returns the number of subscribers using the WatchDog object. */ get subscriberCount(): number { return Object.keys(this._watchdogDataMap).length; } private readonly _watchdogDataMap: { [id: number]: IWatchdogData2 }; private _counter: number; private _currentTime: ArbitraryClockTick; private _timer: NodeJS.Timeout | null; private readonly _visitSubscriberB: (...args: any[]) => void; constructor() { super(); this._watchdogDataMap = {}; this._counter = 0; this._currentTime = this.getCurrentSystemTick(); this._visitSubscriberB = this._visit_subscriber.bind(this); this._timer = null; // as NodeJS.Timer; } /** * add a subscriber to the WatchDog. * * add a subscriber to the WatchDog. * * This method modifies the subscriber be adding a * new method to it called 'keepAlive' * The subscriber must also provide a "watchdogReset". watchdogReset will be called * if the subscriber failed to call keepAlive withing the timeout period. * @param subscriber * @param timeout * @return the numerical key associated with this subscriber */ public addSubscriber(subscriber: ISubscriber, timeout: number): number { this._currentTime = this.getCurrentSystemTick(); timeout = timeout || 1000; assert(typeof timeout === "number", " invalid timeout "); assert(typeof subscriber.watchdogReset === "function", " the subscriber must provide a watchdogReset method "); assert(typeof subscriber.keepAlive !== "function" || subscriber.keepAlive === WatchDog.emptyKeepAlive); this._counter += 1; const key = this._counter; subscriber._watchDog = this; subscriber._watchDogData = { key, lastSeen: this._currentTime, subscriber, timeout, visitCount: 0 } as IWatchdogData2; this._watchdogDataMap[key] = subscriber._watchDogData; if (subscriber.onClientSeen) { subscriber.onClientSeen(); } subscriber.keepAlive = keepAliveFunc.bind(subscriber); // start timer when the first subscriber comes in if (this.subscriberCount === 1) { assert(this._timer === null); this._start_timer(); } assert(this._timer !== null); return key; } public removeSubscriber(subscriber: ISubscriber): void { if (!subscriber._watchDog) { return; // already removed !!! } if (!subscriber._watchDogData) { throw new Error("Internal error"); } assert(subscriber._watchDog instanceof WatchDog); assert(typeof subscriber._watchDogData.key === "number"); assert(typeof subscriber.keepAlive === "function"); assert(Object.prototype.hasOwnProperty.call(this._watchdogDataMap, subscriber._watchDogData.key)); delete this._watchdogDataMap[subscriber._watchDogData.key]; delete subscriber._watchDog; // leave it as it might be useful, delete subscriber._watchDogData; subscriber.keepAlive = WatchDog.emptyKeepAlive; // delete timer when the last subscriber comes out if (this.subscriberCount === 0) { this._stop_timer(); } } public shutdown(): void { assert(this._timer === null && Object.keys(this._watchdogDataMap).length === 0, " leaking subscriber in watchdog"); } public getCurrentSystemTick(): ArbitraryClockTick { return get_clock_tick(); } private _visit_subscriber() { this._currentTime = this.getCurrentSystemTick(); const expiredSubscribers = Object.values(this._watchdogDataMap).filter((watchDogData: IWatchdogData2) => { watchDogData.visitCount += 1; return hasExpired(watchDogData, this._currentTime); }); if (expiredSubscribers.length) { this.emit("timeout", expiredSubscribers); } expiredSubscribers.forEach((watchDogData: IWatchdogData2) => { this.removeSubscriber(watchDogData.subscriber); watchDogData.subscriber.watchdogReset(); }); } private _start_timer(): void { assert(this._timer === null, " setInterval already called ?"); this._timer = setInterval(this._visitSubscriberB, 1000); } private _stop_timer(): void { assert(this._timer !== null, "_stop_timer already called ?"); if (this._timer !== null) { clearInterval(this._timer); this._timer = null; } } }