signalk-edge-link 2.1.0 → 2.1.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/LICENSE +1 -1
- package/lib/connection-config.js +14 -0
- package/lib/instance.js +1 -3
- package/lib/pipeline-v2-client.js +14 -6
- package/lib/routes/monitoring.js +3 -9
- package/package.json +5 -1
package/LICENSE
CHANGED
package/lib/connection-config.js
CHANGED
|
@@ -235,6 +235,20 @@ function validateConnectionConfig(connection, prefix = "") {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
+
// Validate that primary and backup links are different
|
|
239
|
+
const primaryLink = bonding.primary;
|
|
240
|
+
const backupLink = bonding.backup;
|
|
241
|
+
if (primaryLink && backupLink) {
|
|
242
|
+
const sameAddress = primaryLink.address !== undefined &&
|
|
243
|
+
backupLink.address !== undefined &&
|
|
244
|
+
primaryLink.address === backupLink.address;
|
|
245
|
+
const samePort = primaryLink.port !== undefined &&
|
|
246
|
+
backupLink.port !== undefined &&
|
|
247
|
+
primaryLink.port === backupLink.port;
|
|
248
|
+
if (sameAddress && samePort) {
|
|
249
|
+
return `${p}bonding primary and backup links must use different address:port combinations`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
238
252
|
if (bonding.failover !== undefined) {
|
|
239
253
|
if (!bonding.failover ||
|
|
240
254
|
typeof bonding.failover !== "object" ||
|
package/lib/instance.js
CHANGED
|
@@ -93,7 +93,6 @@ function createInstance(app, options, instanceId, pluginId, onStatusChange) {
|
|
|
93
93
|
pipelineServer: null,
|
|
94
94
|
heartbeatHandle: null,
|
|
95
95
|
monitoring: null,
|
|
96
|
-
networkSimulator: null,
|
|
97
96
|
configDebounceTimers: {},
|
|
98
97
|
configContentHashes: {},
|
|
99
98
|
configWatcherObjects: [],
|
|
@@ -269,7 +268,7 @@ function createInstance(app, options, instanceId, pluginId, onStatusChange) {
|
|
|
269
268
|
}
|
|
270
269
|
finally {
|
|
271
270
|
state.batchSendInFlight = false;
|
|
272
|
-
if (state.deltas.length > 0 && !state.pendingRetry) {
|
|
271
|
+
if (state.deltas.length > 0 && !state.pendingRetry && !state.stopped) {
|
|
273
272
|
setImmediate(() => {
|
|
274
273
|
flushDeltaBatch();
|
|
275
274
|
});
|
|
@@ -884,7 +883,6 @@ function createInstance(app, options, instanceId, pluginId, onStatusChange) {
|
|
|
884
883
|
}
|
|
885
884
|
state.monitoring = null;
|
|
886
885
|
}
|
|
887
|
-
state.networkSimulator = null;
|
|
888
886
|
// Stop ping monitor
|
|
889
887
|
if (state.pingMonitor) {
|
|
890
888
|
state.pingMonitor.stop();
|
|
@@ -140,8 +140,11 @@ function createPipelineV2Client(app, state, metricsApi) {
|
|
|
140
140
|
}
|
|
141
141
|
function _effectiveRetransmitAge() {
|
|
142
142
|
let maxAge = retransmitMaxAge;
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
// Use the congestion controller's smoothed RTT (EMA) instead of the raw
|
|
144
|
+
// latest sample to avoid volatile timeout swings from single RTT spikes.
|
|
145
|
+
const smoothedRtt = congestionControl.getAvgRTT();
|
|
146
|
+
if (smoothedRtt > 0) {
|
|
147
|
+
const rttBasedAge = Math.round(smoothedRtt * retransmitRttMultiplier);
|
|
145
148
|
maxAge = Math.min(maxAge, Math.max(retransmitMinAge, rttBasedAge));
|
|
146
149
|
}
|
|
147
150
|
const ackIdleMs = Date.now() - lastAckAt;
|
|
@@ -349,10 +352,12 @@ function createPipelineV2Client(app, state, metricsApi) {
|
|
|
349
352
|
const ackedSeq = packetParser.parseACKPayload(parsed.payload);
|
|
350
353
|
const now = Date.now();
|
|
351
354
|
let rttSample = null;
|
|
352
|
-
//
|
|
355
|
+
// Only sample RTT from packets that were NOT retransmitted (Karn's algorithm).
|
|
356
|
+
// When a retransmitted packet is ACKed, the measurement is ambiguous — the ACK
|
|
357
|
+
// could be for the original or the retransmit — so we skip it entirely.
|
|
353
358
|
const entry = retransmitQueue.get(ackedSeq);
|
|
354
|
-
if (entry &&
|
|
355
|
-
rttSample = Math.max(0, now -
|
|
359
|
+
if (entry && entry.attempts === 0) {
|
|
360
|
+
rttSample = Math.max(0, now - entry.originalTimestamp);
|
|
356
361
|
}
|
|
357
362
|
if (rttSample !== null) {
|
|
358
363
|
metrics.rtt = rttSample;
|
|
@@ -376,10 +381,13 @@ function createPipelineV2Client(app, state, metricsApi) {
|
|
|
376
381
|
lastAckAt = now;
|
|
377
382
|
lastAckRinfo = rinfo ? { address: rinfo.address, port: rinfo.port } : lastAckRinfo;
|
|
378
383
|
// Update congestion control with latest network metrics.
|
|
384
|
+
// Only feed RTT when we have a fresh sample; passing -1 causes the
|
|
385
|
+
// congestion controller's >= 0 guard to skip the RTT EMA update,
|
|
386
|
+
// preventing stale values from being repeatedly folded into the average.
|
|
379
387
|
// Clamp packetLoss to [0, 1] as a defensive measure against any future
|
|
380
388
|
// changes to _calculatePacketLoss that could produce out-of-range values.
|
|
381
389
|
congestionControl.updateMetrics({
|
|
382
|
-
rtt:
|
|
390
|
+
rtt: rttSample ?? -1,
|
|
383
391
|
packetLoss: Math.min(1, Math.max(0, _calculatePacketLoss()))
|
|
384
392
|
});
|
|
385
393
|
app.debug(`ACK received: seq=${ackedSeq}, removed=${removed}, queueDepth=${retransmitQueue.getSize()}, rtt=${metrics.rtt}ms`);
|
package/lib/routes/monitoring.js
CHANGED
|
@@ -336,15 +336,9 @@ function register(router, ctx) {
|
|
|
336
336
|
if (!bundle) {
|
|
337
337
|
return res.status(503).json({ error: "Plugin not started" });
|
|
338
338
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
res.json({
|
|
344
|
-
enabled: true,
|
|
345
|
-
conditions: state.networkSimulator.getConditions(),
|
|
346
|
-
stats: state.networkSimulator.getStats()
|
|
347
|
-
});
|
|
339
|
+
// Network simulator is not currently implemented; endpoint kept for
|
|
340
|
+
// API compatibility and future use.
|
|
341
|
+
res.json({ enabled: false });
|
|
348
342
|
}
|
|
349
343
|
catch (err) {
|
|
350
344
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-edge-link",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "SignalK Edge Link. Secure UDP link for data exchange.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
},
|
|
84
84
|
"author": "Karl-Erik Gustafsson",
|
|
85
85
|
"repository": "https://github.com/KEGustafsson/signalk-edge-link",
|
|
86
|
+
"homepage": "https://github.com/KEGustafsson/signalk-edge-link#readme",
|
|
87
|
+
"bugs": {
|
|
88
|
+
"url": "https://github.com/KEGustafsson/signalk-edge-link/issues"
|
|
89
|
+
},
|
|
86
90
|
"license": "MIT",
|
|
87
91
|
"jest": {
|
|
88
92
|
"testEnvironment": "node",
|