@actuallab/rpc 13.0.163 → 14.0.17
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/index.cjs +50 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +50 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1077,6 +1077,7 @@ var import_core5 = require("@actuallab/core");
|
|
|
1077
1077
|
var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
|
|
1078
1078
|
var DEFAULT_ACK_PERIOD = 256;
|
|
1079
1079
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1080
|
+
var RTT_WINDOW_MS = 2e4;
|
|
1080
1081
|
var RpcStreamSender = class _RpcStreamSender {
|
|
1081
1082
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
1082
1083
|
id;
|
|
@@ -1118,6 +1119,9 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1118
1119
|
_lastAckedIndex = 0;
|
|
1119
1120
|
_abortController = new AbortController();
|
|
1120
1121
|
_iterator = null;
|
|
1122
|
+
// -- RTT sampling (send → matching ack round trips, windowed minimum) --
|
|
1123
|
+
_pendingSendTimes = [];
|
|
1124
|
+
_rttSamples = [];
|
|
1121
1125
|
// -- Recorder-controller metrics (Step 9.1) --
|
|
1122
1126
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1123
1127
|
_skipCount = 0;
|
|
@@ -1142,6 +1146,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1142
1146
|
get skipCount() {
|
|
1143
1147
|
return this._skipCount;
|
|
1144
1148
|
}
|
|
1149
|
+
// Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
|
|
1150
|
+
// RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
|
|
1151
|
+
get minRttMs() {
|
|
1152
|
+
const cutoffMs = Date.now() - RTT_WINDOW_MS;
|
|
1153
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1154
|
+
this._rttSamples.shift();
|
|
1155
|
+
if (this._rttSamples.length === 0)
|
|
1156
|
+
return -1;
|
|
1157
|
+
let min = this._rttSamples[0].rttMs;
|
|
1158
|
+
for (const s of this._rttSamples) {
|
|
1159
|
+
if (s.rttMs < min)
|
|
1160
|
+
min = s.rttMs;
|
|
1161
|
+
}
|
|
1162
|
+
return min;
|
|
1163
|
+
}
|
|
1145
1164
|
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
1146
1165
|
_acks = [];
|
|
1147
1166
|
_whenAckReady = null;
|
|
@@ -1195,6 +1214,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1195
1214
|
this._sendDisconnect();
|
|
1196
1215
|
return;
|
|
1197
1216
|
}
|
|
1217
|
+
this._sampleRtt(nextIndex, mustReset);
|
|
1198
1218
|
this._acks.push({ nextIndex, mustReset });
|
|
1199
1219
|
if (this._whenAckReady) {
|
|
1200
1220
|
this._whenAckReady.resolve();
|
|
@@ -1233,6 +1253,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1233
1253
|
this._nextIndex,
|
|
1234
1254
|
item
|
|
1235
1255
|
);
|
|
1256
|
+
this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
|
|
1236
1257
|
}
|
|
1237
1258
|
this._nextIndex++;
|
|
1238
1259
|
}
|
|
@@ -1451,13 +1472,18 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1451
1472
|
}
|
|
1452
1473
|
}
|
|
1453
1474
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1454
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1475
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1476
|
+
* A reconnect's reset ACK may be drained in one batch with regular
|
|
1477
|
+
* consumption ACKs the client sends right after it, so `mustReset` is
|
|
1478
|
+
* OR-ed over the whole batch — the last ACK alone can't represent it. */
|
|
1455
1479
|
_tryProcessAcks() {
|
|
1456
1480
|
if (this._acks.length === 0) return null;
|
|
1457
|
-
let
|
|
1481
|
+
let nextIndex = 0;
|
|
1482
|
+
let mustReset = false;
|
|
1458
1483
|
while (this._acks.length > 0) {
|
|
1459
1484
|
const a = this._acks.shift();
|
|
1460
|
-
|
|
1485
|
+
nextIndex = a.nextIndex;
|
|
1486
|
+
mustReset ||= a.mustReset;
|
|
1461
1487
|
if (a.mustReset || this._nextIndex < a.nextIndex) {
|
|
1462
1488
|
this._nextIndex = a.nextIndex;
|
|
1463
1489
|
}
|
|
@@ -1466,7 +1492,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1466
1492
|
this.onAckProcessed?.();
|
|
1467
1493
|
} catch {
|
|
1468
1494
|
}
|
|
1469
|
-
return
|
|
1495
|
+
return { nextIndex, mustReset };
|
|
1470
1496
|
}
|
|
1471
1497
|
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1472
1498
|
async _waitAckReady() {
|
|
@@ -1475,6 +1501,26 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1475
1501
|
await this._whenAckReady;
|
|
1476
1502
|
this._whenAckReady = null;
|
|
1477
1503
|
}
|
|
1504
|
+
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1505
|
+
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
1506
|
+
_sampleRtt(ackNextIndex, mustReset) {
|
|
1507
|
+
if (mustReset) {
|
|
1508
|
+
this._pendingSendTimes.length = 0;
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
let sentAtMs = -1;
|
|
1512
|
+
while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
|
|
1513
|
+
sentAtMs = this._pendingSendTimes[0].sentAtMs;
|
|
1514
|
+
this._pendingSendTimes.shift();
|
|
1515
|
+
}
|
|
1516
|
+
if (sentAtMs < 0)
|
|
1517
|
+
return;
|
|
1518
|
+
const nowMs = Date.now();
|
|
1519
|
+
const cutoffMs = nowMs - RTT_WINDOW_MS;
|
|
1520
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1521
|
+
this._rttSamples.shift();
|
|
1522
|
+
this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
|
|
1523
|
+
}
|
|
1478
1524
|
// -- IRpcObject --
|
|
1479
1525
|
reconnect() {
|
|
1480
1526
|
}
|