node-opcua-utils
Version:
pure nodejs OPCUA SDK - module utils
190 lines (158 loc) • 6.13 kB
text/typescript
/**
* @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;
}
}
}