node-opcua-server 2.72.1 → 2.73.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helper.d.ts +10 -0
- package/dist/helper.js +76 -0
- package/dist/helper.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/monitored_item.js +2 -1
- package/dist/monitored_item.js.map +1 -1
- package/dist/opcua_server.js +5 -3
- package/dist/opcua_server.js.map +1 -1
- package/dist/server_capabilities.js +5 -4
- package/dist/server_capabilities.js.map +1 -1
- package/dist/server_engine.d.ts +1 -1
- package/dist/server_engine.js +25 -25
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.d.ts +1 -1
- package/dist/server_publish_engine.js +11 -6
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_publish_engine_for_orphan_subscriptions.js +3 -1
- package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
- package/dist/server_session.d.ts +4 -3
- package/dist/server_session.js +7 -6
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +8 -2
- package/dist/server_subscription.js +70 -59
- package/dist/server_subscription.js.map +1 -1
- package/dist/sessions_compatible_for_transfer.d.ts +1 -1
- package/dist/sessions_compatible_for_transfer.js +3 -0
- package/dist/sessions_compatible_for_transfer.js.map +1 -1
- package/package.json +38 -37
- package/source/helper.ts +87 -0
- package/source/index.ts +2 -1
- package/source/monitored_item.ts +3 -2
- package/source/opcua_server.ts +5 -3
- package/source/server_capabilities.ts +5 -4
- package/source/server_engine.ts +29 -27
- package/source/server_publish_engine.ts +15 -7
- package/source/server_publish_engine_for_orphan_subscriptions.ts +6 -1
- package/source/server_session.ts +15 -13
- package/source/server_subscription.ts +83 -69
- package/source/sessions_compatible_for_transfer.ts +5 -1
package/source/helper.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as util from "util";
|
|
2
|
+
import { OPCUAServer } from "./opcua_server";
|
|
3
|
+
import { ServerEngine } from "./server_engine";
|
|
4
|
+
import { ServerSession } from "./server_session";
|
|
5
|
+
import { Subscription, SubscriptionState } from "./server_subscription";
|
|
6
|
+
|
|
7
|
+
const consolelog = (...args: any) => {
|
|
8
|
+
const d = new Date();
|
|
9
|
+
const t = d.toTimeString().split(" ")[0] + "." + d.getMilliseconds().toString().padStart(3, "0");
|
|
10
|
+
console.log.apply(console, [t, ...args]);
|
|
11
|
+
};
|
|
12
|
+
const doDebug = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
export function installSessionLogging(server: OPCUAServer) {
|
|
19
|
+
installSessionLoggingOnEngine(server.engine);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const info = (subscription: Subscription) => {
|
|
23
|
+
return util.format(
|
|
24
|
+
subscription.subscriptionId,
|
|
25
|
+
SubscriptionState[subscription.state].padEnd(9),
|
|
26
|
+
subscription.state,
|
|
27
|
+
"kac=",
|
|
28
|
+
subscription.currentKeepAliveCount.toString().padStart(3)+
|
|
29
|
+
"/"+
|
|
30
|
+
subscription.maxKeepAliveCount.toString().padStart(3),
|
|
31
|
+
"ltc=",
|
|
32
|
+
subscription.currentLifetimeCount.toString().padStart(3)+
|
|
33
|
+
"/"+
|
|
34
|
+
subscription.lifeTimeCount.toString().padStart(3),
|
|
35
|
+
"prc=",
|
|
36
|
+
subscription.publishEngine?.pendingPublishRequestCount.toString().padStart(3),
|
|
37
|
+
"pi=",
|
|
38
|
+
subscription.publishingInterval
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function installSubscriptionMonitoring(subscription: Subscription) {
|
|
43
|
+
consolelog("new_subscription", subscription.subscriptionId, info(subscription));
|
|
44
|
+
subscription.on("lifeTimeExpired", () => {
|
|
45
|
+
consolelog("lifeTimeExpired".padEnd(45), info(subscription));
|
|
46
|
+
});
|
|
47
|
+
if (true || doDebug) {
|
|
48
|
+
subscription.on("lifeTimeCounterChanged", (ltc: number) => {
|
|
49
|
+
consolelog("subscription lifeTimeCounterChanged".padEnd(45), info(subscription));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
subscription.on("expired", () => {
|
|
53
|
+
consolelog("subscription expired".padEnd(45), info(subscription));
|
|
54
|
+
});
|
|
55
|
+
subscription.on("stateChanged", (state: SubscriptionState) => {
|
|
56
|
+
consolelog("subscription stateChanged".padEnd(45), info(subscription));
|
|
57
|
+
});
|
|
58
|
+
subscription.on("terminate", () => {
|
|
59
|
+
consolelog("subscription terminated".padEnd(45), info(subscription));
|
|
60
|
+
});
|
|
61
|
+
subscription.on("keepalive", () => {
|
|
62
|
+
consolelog("subscription keepalive".padEnd(45), info(subscription));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function installSessionLoggingOnEngine(serverEngine: ServerEngine) {
|
|
66
|
+
function on_create_session(session: ServerSession) {
|
|
67
|
+
try {
|
|
68
|
+
session.on("activate_session", function () {
|
|
69
|
+
consolelog("activate_session");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
session.on("statusChanged", (status) => {
|
|
73
|
+
consolelog("session status changed: ", status);
|
|
74
|
+
});
|
|
75
|
+
session.on("new_subscription", function (subscription) {
|
|
76
|
+
installSubscriptionMonitoring(subscription);
|
|
77
|
+
});
|
|
78
|
+
} catch (err) {
|
|
79
|
+
consolelog((err as any).message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
serverEngine.on("create_session", on_create_session);
|
|
83
|
+
serverEngine.once("session_closed", function (session) {
|
|
84
|
+
consolelog("session is closed");
|
|
85
|
+
serverEngine.removeListener("create_session", on_create_session);
|
|
86
|
+
});
|
|
87
|
+
}
|
package/source/index.ts
CHANGED
package/source/monitored_item.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
coerceTimestampsToReturn,
|
|
26
26
|
sameStatusCode
|
|
27
27
|
} from "node-opcua-data-value";
|
|
28
|
-
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
28
|
+
import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
|
|
29
29
|
import { ExtensionObject } from "node-opcua-extension-object";
|
|
30
30
|
import { NodeId } from "node-opcua-nodeid";
|
|
31
31
|
import { NumericalRange0, NumericRange } from "node-opcua-numeric-range";
|
|
@@ -71,6 +71,7 @@ const defaultItemToMonitor: ReadValueIdOptions = new ReadValueId({
|
|
|
71
71
|
|
|
72
72
|
const debugLog = make_debugLog(__filename);
|
|
73
73
|
const doDebug = checkDebugFlag(__filename);
|
|
74
|
+
const warningLog = make_warningLog(__filename);
|
|
74
75
|
|
|
75
76
|
function _adjust_sampling_interval(samplingInterval: number, node_minimumSamplingInterval: number): number {
|
|
76
77
|
assert(typeof node_minimumSamplingInterval === "number", "expecting a number");
|
|
@@ -216,7 +217,7 @@ function apply_dataChange_filter(this: MonitoredItem, newDataValue: DataValue, o
|
|
|
216
217
|
debugLog("timestampHasChanged ", timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp));
|
|
217
218
|
}
|
|
218
219
|
} catch (err) {
|
|
219
|
-
|
|
220
|
+
warningLog(err);
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
switch (trigger) {
|
package/source/opcua_server.ts
CHANGED
|
@@ -203,6 +203,7 @@ const default_build_info: BuildInfoOptions = {
|
|
|
203
203
|
const minSessionTimeout = 100; // 100 milliseconds
|
|
204
204
|
const defaultSessionTimeout = 1000 * 30; // 30 seconds
|
|
205
205
|
const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes
|
|
206
|
+
let unnamed_session_count = 0;
|
|
206
207
|
|
|
207
208
|
type ResponseClassType =
|
|
208
209
|
| typeof BrowseResponse
|
|
@@ -252,7 +253,7 @@ function moveSessionToChannel(session: ServerSession, channel: ServerSecureChann
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
async function _attempt_to_close_some_old_unactivated_session(server: OPCUAServer) {
|
|
255
|
-
const session = server.engine!.
|
|
256
|
+
const session = server.engine!.getOldestInactiveSession();
|
|
256
257
|
if (session) {
|
|
257
258
|
await server.engine!.closeSession(session.authenticationToken, false, "Forcing");
|
|
258
259
|
}
|
|
@@ -1786,7 +1787,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1786
1787
|
assert(session.sessionTimeout === revisedSessionTimeout);
|
|
1787
1788
|
|
|
1788
1789
|
session.clientDescription = request.clientDescription;
|
|
1789
|
-
session.sessionName = request.sessionName ||
|
|
1790
|
+
session.sessionName = request.sessionName || `<unknown session name ${unnamed_session_count++}>`;
|
|
1790
1791
|
|
|
1791
1792
|
// Depending upon on the SecurityPolicy and the SecurityMode of the SecureChannel, the exchange of
|
|
1792
1793
|
// ApplicationInstanceCertificates and Nonces may be optional and the signatures may be empty. See
|
|
@@ -2239,6 +2240,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2239
2240
|
}
|
|
2240
2241
|
return channel.send_response("MSG", response1, message);
|
|
2241
2242
|
} catch (err) {
|
|
2243
|
+
warningLog(err);
|
|
2242
2244
|
// istanbul ignore next
|
|
2243
2245
|
if (err instanceof Error) {
|
|
2244
2246
|
// istanbul ignore next
|
|
@@ -3136,7 +3138,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3136
3138
|
|
|
3137
3139
|
sendResponse(response);
|
|
3138
3140
|
} catch (err) {
|
|
3139
|
-
|
|
3141
|
+
warningLog(err);
|
|
3140
3142
|
return sendError(StatusCodes.BadInternalError);
|
|
3141
3143
|
}
|
|
3142
3144
|
}
|
|
@@ -154,9 +154,9 @@ export type ServerCapabilitiesOptions = Partial<IServerCapabilities>;
|
|
|
154
154
|
export const defaultServerCapabilities: IServerCapabilities = {
|
|
155
155
|
maxBrowseContinuationPoints: 0,
|
|
156
156
|
maxHistoryContinuationPoints: 0,
|
|
157
|
-
maxStringLength:
|
|
158
|
-
maxArrayLength:
|
|
159
|
-
maxByteStringLength:
|
|
157
|
+
maxStringLength: 16 * 1024 * 1024,
|
|
158
|
+
maxArrayLength: 1024 * 1024,
|
|
159
|
+
maxByteStringLength: 16 * 1024 * 1024,
|
|
160
160
|
maxQueryContinuationPoints: 0,
|
|
161
161
|
|
|
162
162
|
minSupportedSampleRate: 100,
|
|
@@ -245,7 +245,8 @@ export class ServerCapabilities implements IServerCapabilities {
|
|
|
245
245
|
// new in 1.05
|
|
246
246
|
this.maxSessions = options.maxSessions || defaultServerCapabilities.maxSessions;
|
|
247
247
|
|
|
248
|
-
this.maxSubscriptionsPerSession =
|
|
248
|
+
this.maxSubscriptionsPerSession =
|
|
249
|
+
options.maxSubscriptionsPerSession || defaultServerCapabilities.maxSubscriptionsPerSession;
|
|
249
250
|
this.maxSubscriptions = options.maxSubscriptions || defaultServerCapabilities.maxSubscriptions;
|
|
250
251
|
this.maxMonitoredItems = options.maxMonitoredItems || defaultServerCapabilities.maxMonitoredItems;
|
|
251
252
|
this.maxMonitoredItemsPerSubscription =
|
package/source/server_engine.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { EventEmitter } from "events";
|
|
|
5
5
|
import * as async from "async";
|
|
6
6
|
import * as chalk from "chalk";
|
|
7
7
|
import { assert } from "node-opcua-assert";
|
|
8
|
-
|
|
8
|
+
import { BinaryStream} from "node-opcua-binary-stream";
|
|
9
9
|
import {
|
|
10
10
|
addElement,
|
|
11
11
|
AddressSpace,
|
|
@@ -399,6 +399,8 @@ export class ServerEngine extends EventEmitter {
|
|
|
399
399
|
Object.values(this._sessions).forEach((session: ServerSession) => {
|
|
400
400
|
counter += session.currentSubscriptionCount;
|
|
401
401
|
});
|
|
402
|
+
// we also need to add the orphan subscriptions
|
|
403
|
+
counter += this._orphanPublishEngine ? this._orphanPublishEngine .subscriptions.length : 0;
|
|
402
404
|
return counter;
|
|
403
405
|
});
|
|
404
406
|
|
|
@@ -1007,15 +1009,15 @@ export class ServerEngine extends EventEmitter {
|
|
|
1007
1009
|
);
|
|
1008
1010
|
|
|
1009
1011
|
bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxArrayLength, DataType.UInt32, () => {
|
|
1010
|
-
return this.serverCapabilities.maxArrayLength;
|
|
1012
|
+
return Math.min(this.serverCapabilities.maxArrayLength, Variant.maxArrayLength);
|
|
1011
1013
|
});
|
|
1012
1014
|
|
|
1013
1015
|
bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxStringLength, DataType.UInt32, () => {
|
|
1014
|
-
return this.serverCapabilities.maxStringLength;
|
|
1016
|
+
return Math.min(this.serverCapabilities.maxStringLength, BinaryStream.maxStringLength);
|
|
1015
1017
|
});
|
|
1016
1018
|
|
|
1017
1019
|
bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxByteStringLength, DataType.UInt32, () => {
|
|
1018
|
-
return this.serverCapabilities.maxByteStringLength;
|
|
1020
|
+
return Math.min(this.serverCapabilities.maxByteStringLength, BinaryStream.maxByteStringLength);
|
|
1019
1021
|
});
|
|
1020
1022
|
|
|
1021
1023
|
const bindOperationLimits = (operationLimits: OperationLimits) => {
|
|
@@ -1598,13 +1600,17 @@ export class ServerEngine extends EventEmitter {
|
|
|
1598
1600
|
});
|
|
1599
1601
|
}
|
|
1600
1602
|
|
|
1601
|
-
public
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1603
|
+
public getOldestInactiveSession(): ServerSession | null {
|
|
1604
|
+
// search screwed or closed session first
|
|
1605
|
+
let tmp = Object.values(this._sessions).filter(
|
|
1606
|
+
(session1: ServerSession) =>
|
|
1607
|
+
session1.status === "screwed" || session1.status === "disposed" || session1.status === "closed"
|
|
1608
|
+
);
|
|
1605
1609
|
if (tmp.length === 0) {
|
|
1606
|
-
|
|
1610
|
+
// if none available, tap into the session that are not yet activated
|
|
1611
|
+
tmp = Object.values(this._sessions).filter((session1: ServerSession) => session1.status === "new");
|
|
1607
1612
|
}
|
|
1613
|
+
if (tmp.length === 0) return null;
|
|
1608
1614
|
let session = tmp[0];
|
|
1609
1615
|
for (let i = 1; i < tmp.length; i++) {
|
|
1610
1616
|
const c = tmp[i];
|
|
@@ -1812,10 +1818,6 @@ export class ServerEngine extends EventEmitter {
|
|
|
1812
1818
|
if (!subscription) {
|
|
1813
1819
|
return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
|
|
1814
1820
|
}
|
|
1815
|
-
// istanbul ignore next
|
|
1816
|
-
if (!subscription.$session) {
|
|
1817
|
-
return new TransferResult({ statusCode: StatusCodes.BadInternalError });
|
|
1818
|
-
}
|
|
1819
1821
|
|
|
1820
1822
|
// check that session have same userIdentity
|
|
1821
1823
|
if (!sessionsCompatibleForTransfer(subscription.$session, session)) {
|
|
@@ -1846,9 +1848,11 @@ export class ServerEngine extends EventEmitter {
|
|
|
1846
1848
|
|
|
1847
1849
|
const nbSubscriptionBefore = session.publishEngine.subscriptionCount;
|
|
1848
1850
|
|
|
1849
|
-
subscription.$session
|
|
1851
|
+
if (subscription.$session) {
|
|
1852
|
+
subscription.$session._unexposeSubscriptionDiagnostics(subscription);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1850
1855
|
await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
|
|
1851
|
-
|
|
1852
1856
|
subscription.$session = session;
|
|
1853
1857
|
|
|
1854
1858
|
session._exposeSubscriptionDiagnostics(subscription);
|
|
@@ -1983,7 +1987,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1983
1987
|
}
|
|
1984
1988
|
|
|
1985
1989
|
private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
|
|
1986
|
-
debugLog("ServerEngine#_exposeSubscriptionDiagnostics");
|
|
1990
|
+
debugLog("ServerEngine#_exposeSubscriptionDiagnostics", subscription.subscriptionId);
|
|
1987
1991
|
const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
|
|
1988
1992
|
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
|
|
1989
1993
|
assert((subscriptionDiagnostics as any).$subscription === subscription);
|
|
@@ -1995,19 +1999,19 @@ export class ServerEngine extends EventEmitter {
|
|
|
1995
1999
|
}
|
|
1996
2000
|
|
|
1997
2001
|
protected _unexposeSubscriptionDiagnostics(subscription: Subscription): void {
|
|
1998
|
-
const
|
|
2002
|
+
const serverSubscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
|
|
1999
2003
|
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
|
|
2000
2004
|
assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
|
|
2001
|
-
if (subscriptionDiagnostics &&
|
|
2002
|
-
const node = (
|
|
2003
|
-
removeElement(
|
|
2005
|
+
if (subscriptionDiagnostics && serverSubscriptionDiagnosticsArray) {
|
|
2006
|
+
const node = (serverSubscriptionDiagnosticsArray as any)[subscription.id];
|
|
2007
|
+
removeElement(serverSubscriptionDiagnosticsArray, subscriptionDiagnostics);
|
|
2004
2008
|
/*assert(
|
|
2005
2009
|
!(subscriptionDiagnosticsArray as any)[subscription.id],
|
|
2006
2010
|
" subscription node must have been removed from subscriptionDiagnosticsArray"
|
|
2007
2011
|
);
|
|
2008
2012
|
*/
|
|
2009
2013
|
}
|
|
2010
|
-
debugLog("ServerEngine#_unexposeSubscriptionDiagnostics");
|
|
2014
|
+
debugLog("ServerEngine#_unexposeSubscriptionDiagnostics", subscription.subscriptionId);
|
|
2011
2015
|
}
|
|
2012
2016
|
|
|
2013
2017
|
/**
|
|
@@ -2200,16 +2204,14 @@ export class ServerEngine extends EventEmitter {
|
|
|
2200
2204
|
private _getServerSubscriptionDiagnosticsArrayNode(): UADynamicVariableArray<SubscriptionDiagnosticsDataType> | null {
|
|
2201
2205
|
// istanbul ignore next
|
|
2202
2206
|
if (!this.addressSpace) {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
}
|
|
2207
|
+
doDebug && debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray : no addressSpace");
|
|
2208
|
+
|
|
2206
2209
|
return null; // no addressSpace
|
|
2207
2210
|
}
|
|
2208
2211
|
const subscriptionDiagnosticsType = this.addressSpace.findVariableType("SubscriptionDiagnosticsType");
|
|
2209
2212
|
if (!subscriptionDiagnosticsType) {
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
}
|
|
2213
|
+
doDebug &&
|
|
2214
|
+
debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray " + ": cannot find SubscriptionDiagnosticsType");
|
|
2213
2215
|
}
|
|
2214
2216
|
|
|
2215
2217
|
// SubscriptionDiagnosticsArray = i=2290
|
|
@@ -116,7 +116,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
116
116
|
subscription: Subscription,
|
|
117
117
|
destPublishEngine: ServerSidePublishEngine,
|
|
118
118
|
sendInitialValues: boolean
|
|
119
|
-
): Promise<
|
|
119
|
+
): Promise<Subscription> {
|
|
120
120
|
const srcPublishEngine = subscription.publishEngine as any as ServerSidePublishEngine;
|
|
121
121
|
|
|
122
122
|
assert(!destPublishEngine.getSubscriptionById(subscription.id));
|
|
@@ -135,7 +135,9 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
135
135
|
// to the old Session.
|
|
136
136
|
subscription.notifyTransfer();
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
const tmp = srcPublishEngine.detach_subscription(subscription);
|
|
139
|
+
destPublishEngine.add_subscription(tmp);
|
|
140
|
+
|
|
139
141
|
subscription.resetLifeTimeCounter();
|
|
140
142
|
if (sendInitialValues) {
|
|
141
143
|
/* A Boolean parameter with the following values:
|
|
@@ -158,6 +160,8 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
158
160
|
|
|
159
161
|
assert(destPublishEngine.getSubscriptionById(subscription.id));
|
|
160
162
|
assert(!srcPublishEngine.getSubscriptionById(subscription.id));
|
|
163
|
+
|
|
164
|
+
return subscription;
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
public maxPublishRequestInQueue = 0;
|
|
@@ -321,16 +325,16 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
321
325
|
}
|
|
322
326
|
|
|
323
327
|
public on_close_subscription(subscription: IClosedOrTransferredSubscription): void {
|
|
324
|
-
debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
|
|
328
|
+
doDebug && debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
|
|
325
329
|
if (subscription.hasPendingNotifications) {
|
|
326
|
-
debugLog(
|
|
330
|
+
doDebug && debugLog(
|
|
327
331
|
"ServerSidePublishEngine#on_close_subscription storing subscription",
|
|
328
332
|
subscription.id,
|
|
329
333
|
" to _closed_subscriptions because it has pending notification"
|
|
330
334
|
);
|
|
331
335
|
this._closed_subscriptions.push(subscription);
|
|
332
336
|
} else {
|
|
333
|
-
debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
|
|
337
|
+
doDebug && debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
|
|
334
338
|
// subscription is no longer needed
|
|
335
339
|
subscription.dispose();
|
|
336
340
|
}
|
|
@@ -357,7 +361,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
357
361
|
public findLateSubscriptions(): Subscription[] {
|
|
358
362
|
const subscriptions = Object.values(this._subscriptions);
|
|
359
363
|
return subscriptions.filter((subscription: Subscription) => {
|
|
360
|
-
return subscription.state === SubscriptionState.LATE && subscription.publishingEnabled;
|
|
364
|
+
return (subscription.state === SubscriptionState.LATE || !subscription.messageSent) && subscription.publishingEnabled;
|
|
361
365
|
});
|
|
362
366
|
}
|
|
363
367
|
|
|
@@ -479,6 +483,9 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
479
483
|
s.timeToKeepAlive +
|
|
480
484
|
" m?=" +
|
|
481
485
|
s.hasUncollectedMonitoredItemNotifications +
|
|
486
|
+
" " +
|
|
487
|
+
SubscriptionState[s.state] +
|
|
488
|
+
" " + s.messageSent +
|
|
482
489
|
"]"
|
|
483
490
|
)
|
|
484
491
|
.join(" \n")
|
|
@@ -493,6 +500,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
493
500
|
const starving_subscription = /* this.findSubscriptionWaitingForFirstPublish() || */ findLateSubscriptionSortedByPriority();
|
|
494
501
|
return starving_subscription;
|
|
495
502
|
}
|
|
503
|
+
|
|
496
504
|
private _feed_late_subscription() {
|
|
497
505
|
setImmediate(() => {
|
|
498
506
|
if (!this.pendingPublishRequestCount) {
|
|
@@ -500,7 +508,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
500
508
|
}
|
|
501
509
|
const starving_subscription = this._find_starving_subscription();
|
|
502
510
|
if (starving_subscription) {
|
|
503
|
-
debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId = "), starving_subscription.id);
|
|
511
|
+
doDebug && debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId = "), starving_subscription.id);
|
|
504
512
|
starving_subscription.process_subscription();
|
|
505
513
|
}
|
|
506
514
|
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as chalk from "chalk";
|
|
6
6
|
|
|
7
7
|
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
8
|
+
import { NodeId } from "node-opcua-nodeid";
|
|
8
9
|
|
|
9
10
|
import { ServerSidePublishEngine, ServerSidePublishEngineOptions } from "./server_publish_engine";
|
|
10
11
|
import { Subscription } from "./server_subscription";
|
|
@@ -28,6 +29,10 @@ export class ServerSidePublishEngineForOrphanSubscription extends ServerSidePubl
|
|
|
28
29
|
|
|
29
30
|
public add_subscription(subscription: Subscription): Subscription {
|
|
30
31
|
debugLog(chalk.bgCyan.yellow.bold(" adding live subscription with id="), subscription.id, " to orphan");
|
|
32
|
+
|
|
33
|
+
// detach subscription from old seession
|
|
34
|
+
subscription.$session = undefined;
|
|
35
|
+
|
|
31
36
|
super.add_subscription(subscription);
|
|
32
37
|
// also add an event handler to detected when the subscription has ended
|
|
33
38
|
// so we can automatically remove it from the orphan table
|
|
@@ -38,7 +43,7 @@ export class ServerSidePublishEngineForOrphanSubscription extends ServerSidePubl
|
|
|
38
43
|
// xx publish_engine.detach_subscription(subscription);
|
|
39
44
|
// Xx subscription.dispose();
|
|
40
45
|
};
|
|
41
|
-
subscription.
|
|
46
|
+
subscription.once("expired", (subscription as any)._expired_func);
|
|
42
47
|
return subscription;
|
|
43
48
|
}
|
|
44
49
|
|
package/source/server_session.ts
CHANGED
|
@@ -72,6 +72,7 @@ interface SessionSecurityDiagnosticsDataTypeEx extends SessionSecurityDiagnostic
|
|
|
72
72
|
$session: any;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export type SessionStatus = "new" | "active" | "screwed" | "disposed" | "closed";
|
|
75
76
|
/**
|
|
76
77
|
*
|
|
77
78
|
* A Server session object.
|
|
@@ -99,13 +100,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
99
100
|
public static registry = new ObjectRegistry();
|
|
100
101
|
public static maxPublishRequestInQueue = 100;
|
|
101
102
|
|
|
102
|
-
public __status = "";
|
|
103
|
+
public __status: SessionStatus = "new";
|
|
103
104
|
public parent: ServerEngine;
|
|
104
105
|
public authenticationToken: NodeId;
|
|
105
106
|
public nodeId: NodeId;
|
|
106
107
|
public sessionName = "";
|
|
107
108
|
|
|
108
|
-
|
|
109
109
|
public publishEngine: ServerSidePublishEngine;
|
|
110
110
|
public sessionObject: any;
|
|
111
111
|
public readonly creationDate: Date;
|
|
@@ -227,18 +227,21 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
227
227
|
* the first transaction is the creation of the session
|
|
228
228
|
*/
|
|
229
229
|
public get clientLastContactTime(): number {
|
|
230
|
-
const lastSeen =
|
|
230
|
+
const lastSeen = this._watchDogData ? this._watchDogData.lastSeen : minOPCUADate.getTime();
|
|
231
231
|
return WatchDog.lastSeenToDuration(lastSeen);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
public get status():
|
|
234
|
+
public get status(): SessionStatus {
|
|
235
235
|
return this.__status;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
public set status(value:
|
|
238
|
+
public set status(value: SessionStatus) {
|
|
239
239
|
if (value === "active") {
|
|
240
240
|
this._createSessionObjectInAddressSpace();
|
|
241
241
|
}
|
|
242
|
+
if (this.__status !== value) {
|
|
243
|
+
this.emit("statusChanged", value);
|
|
244
|
+
}
|
|
242
245
|
this.__status = value;
|
|
243
246
|
}
|
|
244
247
|
|
|
@@ -304,7 +307,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
304
307
|
const propName = lowerFirstLetter(counterName + "Count");
|
|
305
308
|
// istanbul ignore next
|
|
306
309
|
if (!Object.prototype.hasOwnProperty.call(this._sessionDiagnostics, propName)) {
|
|
307
|
-
|
|
310
|
+
errorLog("incrementRequestTotalCounter: cannot find", propName);
|
|
308
311
|
// xx return;
|
|
309
312
|
} else {
|
|
310
313
|
(this._sessionDiagnostics as any)[propName].totalCount += 1;
|
|
@@ -444,7 +447,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
444
447
|
assert(this.currentSubscriptionCount === 0);
|
|
445
448
|
|
|
446
449
|
this.status = "closed";
|
|
447
|
-
|
|
450
|
+
|
|
448
451
|
this._detach_channel();
|
|
449
452
|
|
|
450
453
|
/**
|
|
@@ -559,10 +562,10 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
559
562
|
|
|
560
563
|
public _detach_channel(): void {
|
|
561
564
|
const channel = this.channel;
|
|
562
|
-
|
|
565
|
+
|
|
563
566
|
// istanbul ignore next
|
|
564
567
|
if (!channel) {
|
|
565
|
-
return;
|
|
568
|
+
return;
|
|
566
569
|
// already detached !
|
|
567
570
|
// throw new Error("expecting a valid channel");
|
|
568
571
|
}
|
|
@@ -682,13 +685,13 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
682
685
|
|
|
683
686
|
Object.defineProperty(this._sessionDiagnostics, "sessionId", {
|
|
684
687
|
get(this: SessionDiagnosticsDataTypeEx) {
|
|
685
|
-
return this.$session
|
|
688
|
+
return this.$session ? this.$session.nodeId : NodeId.nullNodeId;
|
|
686
689
|
}
|
|
687
690
|
});
|
|
688
691
|
|
|
689
692
|
Object.defineProperty(this._sessionDiagnostics, "sessionName", {
|
|
690
693
|
get(this: SessionDiagnosticsDataTypeEx) {
|
|
691
|
-
return this.$session
|
|
694
|
+
return this.$session ? this.$session.sessionName.toString() : "";
|
|
692
695
|
}
|
|
693
696
|
});
|
|
694
697
|
|
|
@@ -910,8 +913,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
910
913
|
assert(this.nodeId instanceof NodeId);
|
|
911
914
|
|
|
912
915
|
subscription.$session = this;
|
|
913
|
-
|
|
914
|
-
subscription.sessionId = this.nodeId;
|
|
916
|
+
assert(subscription.sessionId === this.nodeId);
|
|
915
917
|
|
|
916
918
|
this._cumulatedSubscriptionCount += 1;
|
|
917
919
|
|