@actuallab/rpc 14.3.34 → 14.4.11
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 +225 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -11
- package/dist/index.d.ts +28 -11
- package/dist/index.js +225 -146
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -993,7 +993,8 @@ var RpcCallTimeouts = class _RpcCallTimeouts {
|
|
|
993
993
|
static None = new _RpcCallTimeouts(Infinity, Infinity);
|
|
994
994
|
/** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
|
|
995
995
|
static Command = new _RpcCallTimeouts(1500, 1e4);
|
|
996
|
-
/** Max time a call may wait for
|
|
996
|
+
/** Max time a call may wait for a connection - before it is sent, and again for the
|
|
997
|
+
* duration of any outage it is still pending through. */
|
|
997
998
|
connectTimeoutMs;
|
|
998
999
|
/** Max time a sent call may stay unanswered before it is failed. */
|
|
999
1000
|
runTimeoutMs;
|
|
@@ -1192,13 +1193,17 @@ var RpcSystemCallHandler = class {
|
|
|
1192
1193
|
case RpcSystemCalls.keepAlive: {
|
|
1193
1194
|
peer.notifyKeepAliveReceived();
|
|
1194
1195
|
const keepAliveIds = args[0];
|
|
1195
|
-
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.
|
|
1196
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.wireConnection !== void 0) {
|
|
1196
1197
|
const unknownIds = [];
|
|
1197
1198
|
for (const id of keepAliveIds)
|
|
1198
1199
|
if (peer.sharedObjects.get(id) === void 0)
|
|
1199
1200
|
unknownIds.push(id);
|
|
1200
1201
|
if (unknownIds.length > 0)
|
|
1201
|
-
peer.hub.systemCallSender.disconnect(
|
|
1202
|
+
peer.hub.systemCallSender.disconnect(
|
|
1203
|
+
peer.wireConnection,
|
|
1204
|
+
peer.serializationFormat,
|
|
1205
|
+
unknownIds
|
|
1206
|
+
);
|
|
1202
1207
|
}
|
|
1203
1208
|
break;
|
|
1204
1209
|
}
|
|
@@ -1244,16 +1249,24 @@ var RpcSystemCallHandler = class {
|
|
|
1244
1249
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1245
1250
|
if (sender)
|
|
1246
1251
|
sender.onAck(args[0], args[1]);
|
|
1247
|
-
else if (peer.
|
|
1248
|
-
peer.hub.systemCallSender.disconnect(
|
|
1252
|
+
else if (peer.wireConnection !== void 0)
|
|
1253
|
+
peer.hub.systemCallSender.disconnect(
|
|
1254
|
+
peer.wireConnection,
|
|
1255
|
+
peer.serializationFormat,
|
|
1256
|
+
[relatedId]
|
|
1257
|
+
);
|
|
1249
1258
|
break;
|
|
1250
1259
|
}
|
|
1251
1260
|
case RpcSystemCalls.ackEnd: {
|
|
1252
1261
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1253
1262
|
if (sender)
|
|
1254
1263
|
sender.onAckEnd(args[0]);
|
|
1255
|
-
else if (peer.
|
|
1256
|
-
peer.hub.systemCallSender.disconnect(
|
|
1264
|
+
else if (peer.wireConnection !== void 0)
|
|
1265
|
+
peer.hub.systemCallSender.disconnect(
|
|
1266
|
+
peer.wireConnection,
|
|
1267
|
+
peer.serializationFormat,
|
|
1268
|
+
[relatedId]
|
|
1269
|
+
);
|
|
1257
1270
|
break;
|
|
1258
1271
|
}
|
|
1259
1272
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1282,12 +1295,13 @@ var RpcSystemCallHandler = class {
|
|
|
1282
1295
|
* with the same `RelatedId`.
|
|
1283
1296
|
*/
|
|
1284
1297
|
_handleReconnect(relatedId, args, peer) {
|
|
1285
|
-
|
|
1298
|
+
const conn = peer.wireConnection;
|
|
1299
|
+
if (conn === void 0) return;
|
|
1286
1300
|
const handshakeIndex = args[0];
|
|
1287
1301
|
if (typeof handshakeIndex !== "number" || handshakeIndex !== peer.ownHandshakeIndex) {
|
|
1288
1302
|
const got = typeof handshakeIndex === "number" ? handshakeIndex : `<${typeof handshakeIndex}>`;
|
|
1289
1303
|
peer.hub.systemCallSender.error(
|
|
1290
|
-
|
|
1304
|
+
conn,
|
|
1291
1305
|
peer.serializationFormat,
|
|
1292
1306
|
relatedId,
|
|
1293
1307
|
new Error(
|
|
@@ -1298,7 +1312,7 @@ var RpcSystemCallHandler = class {
|
|
|
1298
1312
|
}
|
|
1299
1313
|
if (!peer.tryClaimReconnect()) {
|
|
1300
1314
|
peer.hub.systemCallSender.error(
|
|
1301
|
-
|
|
1315
|
+
conn,
|
|
1302
1316
|
peer.serializationFormat,
|
|
1303
1317
|
relatedId,
|
|
1304
1318
|
new Error("TooLateToReconnect: this connection was already reconnected")
|
|
@@ -1329,7 +1343,7 @@ var RpcSystemCallHandler = class {
|
|
|
1329
1343
|
}
|
|
1330
1344
|
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1331
1345
|
const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1332
|
-
peer.hub.systemCallSender.ok(
|
|
1346
|
+
peer.hub.systemCallSender.ok(conn, peer.serializationFormat, relatedId, responseValue);
|
|
1333
1347
|
}
|
|
1334
1348
|
};
|
|
1335
1349
|
function polymorphicPayloadError(method, got, expected) {
|
|
@@ -1512,6 +1526,8 @@ var import_core6 = require("@actuallab/core");
|
|
|
1512
1526
|
// src/rpc-stream-sender.ts
|
|
1513
1527
|
var import_core5 = require("@actuallab/core");
|
|
1514
1528
|
var { warnLog: warnLog5 } = getLogs("RpcSharedStream");
|
|
1529
|
+
function _ignore() {
|
|
1530
|
+
}
|
|
1515
1531
|
var DEFAULT_ACK_PERIOD = 256;
|
|
1516
1532
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1517
1533
|
var RTT_WINDOW_MS = 2e4;
|
|
@@ -1551,7 +1567,11 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1551
1567
|
// -- Pump state --
|
|
1552
1568
|
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1553
1569
|
_nextIndex = 0;
|
|
1554
|
-
|
|
1570
|
+
/** Torn down — `disconnect()` ran. .NET's counterpart is disposal; note it
|
|
1571
|
+
* does NOT mean "an End was sent", which .NET does not track at all. */
|
|
1572
|
+
_isDisposed = false;
|
|
1573
|
+
/** The pump has returned. .NET's `WhenRunning.IsCompleted`. */
|
|
1574
|
+
_isCompleted = false;
|
|
1555
1575
|
_started = new import_core5.PromiseSource();
|
|
1556
1576
|
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1557
1577
|
_lastAckedIndex = 0;
|
|
@@ -1658,7 +1678,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1658
1678
|
this._sendDisconnect();
|
|
1659
1679
|
return;
|
|
1660
1680
|
}
|
|
1661
|
-
} else if (this.
|
|
1681
|
+
} else if (this._isCompleted) {
|
|
1662
1682
|
this._sendDisconnect();
|
|
1663
1683
|
return;
|
|
1664
1684
|
} else if (mustReset && !this.allowReconnect) {
|
|
@@ -1679,7 +1699,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1679
1699
|
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1680
1700
|
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1681
1701
|
_sendDisconnect() {
|
|
1682
|
-
const conn = this.peer.
|
|
1702
|
+
const conn = this.peer.wireConnection;
|
|
1683
1703
|
if (conn)
|
|
1684
1704
|
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1685
1705
|
this.disconnect();
|
|
@@ -1693,13 +1713,13 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1693
1713
|
*
|
|
1694
1714
|
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1695
1715
|
* the item is considered "sent" from the pump's perspective so the replay
|
|
1696
|
-
* buffer indexing stays consistent. If
|
|
1716
|
+
* buffer indexing stays consistent. If the peer isn't connected, the wire
|
|
1697
1717
|
* message is dropped; the item will be resent from the replay buffer
|
|
1698
1718
|
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1699
1719
|
*/
|
|
1700
1720
|
sendItem(item) {
|
|
1701
|
-
if (this.
|
|
1702
|
-
const conn = this.peer.
|
|
1721
|
+
if (this._isDisposed) return;
|
|
1722
|
+
const conn = this.peer.wireConnection;
|
|
1703
1723
|
if (conn) {
|
|
1704
1724
|
this.peer.hub.systemCallSender.item(
|
|
1705
1725
|
conn,
|
|
@@ -1716,8 +1736,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1716
1736
|
}
|
|
1717
1737
|
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1718
1738
|
sendBatch(items) {
|
|
1719
|
-
if (this.
|
|
1720
|
-
const conn = this.peer.
|
|
1739
|
+
if (this._isDisposed || items.length === 0) return;
|
|
1740
|
+
const conn = this.peer.wireConnection;
|
|
1721
1741
|
if (conn) {
|
|
1722
1742
|
this.peer.hub.systemCallSender.batch(
|
|
1723
1743
|
conn,
|
|
@@ -1729,18 +1749,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1729
1749
|
}
|
|
1730
1750
|
this._nextIndex += items.length;
|
|
1731
1751
|
}
|
|
1732
|
-
/** Signal stream completion to the client.
|
|
1752
|
+
/** Signal stream completion to the client. Re-sendable on purpose: if the
|
|
1753
|
+
* peer was not connected the frame is dropped, and the End stays in the
|
|
1754
|
+
* replay buffer for the next reset ack — .NET has no "already ended" latch
|
|
1755
|
+
* (the End is an ordinary buffer entry, RpcSharedStream.cs:185/239). */
|
|
1733
1756
|
sendEnd(error) {
|
|
1734
|
-
if (this.
|
|
1735
|
-
|
|
1736
|
-
const conn = this.peer.
|
|
1757
|
+
if (this._isDisposed) return;
|
|
1758
|
+
const index = this._nextIndex++;
|
|
1759
|
+
const conn = this.peer.wireConnection;
|
|
1737
1760
|
if (!conn) return;
|
|
1738
1761
|
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1739
1762
|
this.peer.hub.systemCallSender.end(
|
|
1740
1763
|
conn,
|
|
1741
1764
|
this.peer.serializationFormat,
|
|
1742
1765
|
this.id.localId,
|
|
1743
|
-
|
|
1766
|
+
index,
|
|
1744
1767
|
errorInfo
|
|
1745
1768
|
);
|
|
1746
1769
|
}
|
|
@@ -1752,13 +1775,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1752
1775
|
*/
|
|
1753
1776
|
async writeFrom(source) {
|
|
1754
1777
|
await this._started;
|
|
1755
|
-
if (this.
|
|
1778
|
+
if (this._isDisposed) return;
|
|
1756
1779
|
const iterator = source[Symbol.asyncIterator]();
|
|
1757
1780
|
this._iterator = iterator;
|
|
1758
1781
|
const state = { iteratorDone: false };
|
|
1759
1782
|
try {
|
|
1760
1783
|
await this._run(iterator, state);
|
|
1761
1784
|
} finally {
|
|
1785
|
+
this._isCompleted = true;
|
|
1762
1786
|
if (state.iteratorDone && this._iterator === iterator) {
|
|
1763
1787
|
this._iterator = null;
|
|
1764
1788
|
}
|
|
@@ -1771,7 +1795,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1771
1795
|
* With `bufferSize > ackAdvance` the local ring buffer holds more than the
|
|
1772
1796
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1773
1797
|
*/
|
|
1774
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition --
|
|
1798
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _isDisposed changes across awaits */
|
|
1775
1799
|
async _run(iterator, state) {
|
|
1776
1800
|
const buffer = new import_core5.RingBuffer(this.bufferSize + 1);
|
|
1777
1801
|
const isRealTime = this.isRealTime;
|
|
@@ -1804,7 +1828,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1804
1828
|
if (r === pending)
|
|
1805
1829
|
return false;
|
|
1806
1830
|
whenMovedNext = null;
|
|
1807
|
-
if (this.
|
|
1831
|
+
if (this._isDisposed) return false;
|
|
1808
1832
|
if (r.done) {
|
|
1809
1833
|
buffer.pushTail(_endItem);
|
|
1810
1834
|
isFullyBuffered = true;
|
|
@@ -1828,105 +1852,107 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1828
1852
|
return true;
|
|
1829
1853
|
}
|
|
1830
1854
|
};
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
await this._waitAckReady();
|
|
1835
|
-
if (this._ended) return;
|
|
1836
|
-
ack = this._tryProcessAcks();
|
|
1855
|
+
nextAck:
|
|
1856
|
+
while (true) {
|
|
1857
|
+
let ack = this._tryProcessAcks();
|
|
1837
1858
|
if (!ack) {
|
|
1838
|
-
|
|
1839
|
-
return;
|
|
1859
|
+
await this._waitAckReady();
|
|
1860
|
+
if (this._isDisposed) return;
|
|
1861
|
+
ack = this._tryProcessAcks();
|
|
1862
|
+
if (!ack) {
|
|
1863
|
+
warnLog5?.log("Something is off: couldn't read an acknowledgement");
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1840
1866
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1867
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1868
|
+
if (isRealTime && ack.mustReset && !isFullyBuffered) {
|
|
1869
|
+
buffer.clear();
|
|
1870
|
+
bufferStart = this._nextIndex;
|
|
1871
|
+
while (true) {
|
|
1872
|
+
if (this._isDisposed) return;
|
|
1873
|
+
let item;
|
|
1874
|
+
let accepted = false;
|
|
1875
|
+
try {
|
|
1876
|
+
const r = await readNext();
|
|
1877
|
+
if (this._isDisposed) return;
|
|
1878
|
+
if (r.done) {
|
|
1879
|
+
item = _endItem;
|
|
1880
|
+
isFullyBuffered = true;
|
|
1881
|
+
state.iteratorDone = true;
|
|
1882
|
+
} else if (this.canSkipTo(r.value)) {
|
|
1883
|
+
item = { kind: "value", value: r.value };
|
|
1884
|
+
accepted = true;
|
|
1885
|
+
} else {
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
} catch (e) {
|
|
1856
1889
|
state.iteratorDone = true;
|
|
1857
|
-
|
|
1858
|
-
item = {
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1890
|
+
isFullyBuffered = true;
|
|
1891
|
+
item = {
|
|
1892
|
+
kind: "error",
|
|
1893
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1894
|
+
};
|
|
1862
1895
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
isFullyBuffered
|
|
1866
|
-
item = {
|
|
1867
|
-
kind: "error",
|
|
1868
|
-
error: e instanceof Error ? e : new Error(String(e))
|
|
1869
|
-
};
|
|
1896
|
+
buffer.pushTail(item);
|
|
1897
|
+
fireBuffered();
|
|
1898
|
+
if (accepted || isFullyBuffered) break;
|
|
1870
1899
|
}
|
|
1871
|
-
buffer.pushTail(item);
|
|
1872
|
-
fireBuffered();
|
|
1873
|
-
if (accepted || isFullyBuffered) break;
|
|
1874
1900
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
bufferStart += shift;
|
|
1880
|
-
}
|
|
1881
|
-
if (this._nextIndex < bufferStart) {
|
|
1882
|
-
if (!this._ended)
|
|
1883
|
-
this.sendEnd(new Error("Stream position unavailable."));
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
let bufferIndex = this._nextIndex - bufferStart;
|
|
1887
|
-
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1888
|
-
while (this._nextIndex < maxIndex) {
|
|
1889
|
-
if (this._ended) return;
|
|
1890
|
-
if (isRealTime) {
|
|
1891
|
-
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1892
|
-
if (!await bufferNext(true, true))
|
|
1893
|
-
break;
|
|
1894
|
-
}
|
|
1895
|
-
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1896
|
-
await bufferNext(false, true);
|
|
1897
|
-
}
|
|
1898
|
-
if (ack.nextIndex > 0) {
|
|
1899
|
-
const result = _compactBufferedUnsentSuffix(
|
|
1900
|
-
buffer,
|
|
1901
|
-
bufferIndex,
|
|
1902
|
-
this.canSkipTo
|
|
1903
|
-
);
|
|
1904
|
-
bufferIndex = result.newIndex;
|
|
1905
|
-
if (result.skipped > 0)
|
|
1906
|
-
this._skipCount += result.skipped;
|
|
1907
|
-
}
|
|
1908
|
-
if (this._hasPendingAck) break;
|
|
1901
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1902
|
+
if (shift > 0) {
|
|
1903
|
+
buffer.moveHead(shift);
|
|
1904
|
+
bufferStart += shift;
|
|
1909
1905
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1906
|
+
if (this._nextIndex < bufferStart) {
|
|
1907
|
+
if (!this._isDisposed)
|
|
1908
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1912
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1913
|
+
while (this._nextIndex < maxIndex) {
|
|
1914
|
+
if (this._isDisposed) return;
|
|
1915
|
+
if (isRealTime) {
|
|
1916
|
+
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1917
|
+
if (!await bufferNext(true, true))
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1921
|
+
whenMovedNext ??= iterator.next();
|
|
1922
|
+
const whenAckReady = this._whenAckReady ??= new import_core5.PromiseSource();
|
|
1923
|
+
await Promise.race([whenMovedNext.then(_ignore, _ignore), whenAckReady]);
|
|
1924
|
+
if (!this._hasPendingAck)
|
|
1925
|
+
await bufferNext(false, true);
|
|
1926
|
+
}
|
|
1927
|
+
if (ack.nextIndex > 0) {
|
|
1928
|
+
const result = _compactBufferedUnsentSuffix(
|
|
1929
|
+
buffer,
|
|
1930
|
+
bufferIndex,
|
|
1931
|
+
this.canSkipTo
|
|
1932
|
+
);
|
|
1933
|
+
bufferIndex = result.newIndex;
|
|
1934
|
+
if (result.skipped > 0)
|
|
1935
|
+
this._skipCount += result.skipped;
|
|
1936
|
+
}
|
|
1937
|
+
if (this._hasPendingAck) break;
|
|
1920
1938
|
}
|
|
1939
|
+
while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {
|
|
1940
|
+
const item = buffer.get(bufferIndex++);
|
|
1941
|
+
if (item.kind === "value") {
|
|
1942
|
+
this.sendItem(item.value);
|
|
1943
|
+
} else {
|
|
1944
|
+
this.sendEnd(item.kind === "error" ? item.error : void 0);
|
|
1945
|
+
continue nextAck;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
if (this._nextIndex >= maxIndex)
|
|
1949
|
+
break;
|
|
1950
|
+
if (isFullyBuffered) break;
|
|
1951
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1952
|
+
if (!isRealTime)
|
|
1953
|
+
await bufferNext(false, false);
|
|
1921
1954
|
}
|
|
1922
|
-
if (this._nextIndex >= maxIndex)
|
|
1923
|
-
break;
|
|
1924
|
-
if (isFullyBuffered) break;
|
|
1925
|
-
if (!buffer.hasRemainingCapacity) break;
|
|
1926
|
-
if (!isRealTime)
|
|
1927
|
-
await bufferNext(false, false);
|
|
1928
1955
|
}
|
|
1929
|
-
}
|
|
1930
1956
|
}
|
|
1931
1957
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1932
1958
|
get _hasPendingAck() {
|
|
@@ -1951,13 +1977,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1951
1977
|
}
|
|
1952
1978
|
return { nextIndex, mustReset };
|
|
1953
1979
|
}
|
|
1954
|
-
/** Wait until an ACK is pending (or
|
|
1980
|
+
/** Wait until an ACK is pending (or the sender is torn down). */
|
|
1955
1981
|
async _waitAckReady() {
|
|
1956
|
-
if (this._hasPendingAck || this.
|
|
1982
|
+
if (this._hasPendingAck || this._isDisposed)
|
|
1957
1983
|
return;
|
|
1958
|
-
this._whenAckReady
|
|
1959
|
-
await
|
|
1960
|
-
this._whenAckReady
|
|
1984
|
+
const whenAckReady = this._whenAckReady ??= new import_core5.PromiseSource();
|
|
1985
|
+
await whenAckReady;
|
|
1986
|
+
if (this._whenAckReady === whenAckReady)
|
|
1987
|
+
this._whenAckReady = null;
|
|
1961
1988
|
}
|
|
1962
1989
|
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1963
1990
|
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
@@ -1983,8 +2010,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1983
2010
|
reconnect() {
|
|
1984
2011
|
}
|
|
1985
2012
|
disconnect() {
|
|
1986
|
-
if (this.
|
|
1987
|
-
this.
|
|
2013
|
+
if (this._isDisposed) return;
|
|
2014
|
+
this._isDisposed = true;
|
|
1988
2015
|
this._abortController.abort();
|
|
1989
2016
|
if (!this._started.isCompleted) {
|
|
1990
2017
|
this._started.resolve();
|
|
@@ -2143,8 +2170,11 @@ var RpcStream = class _RpcStream {
|
|
|
2143
2170
|
}
|
|
2144
2171
|
}
|
|
2145
2172
|
/**
|
|
2146
|
-
* For local streams: resolves
|
|
2147
|
-
*
|
|
2173
|
+
* For local streams: resolves once the consumer acknowledges the end
|
|
2174
|
+
* ($sys.AckEnd) or the sender is disconnected — NOT when the source drains,
|
|
2175
|
+
* since the sender keeps serving acks after End so a reconnect can replay it.
|
|
2176
|
+
* A source error reaches the consumer as $sys.End(error) instead of rejecting
|
|
2177
|
+
* this. Throws if `toRef()` hasn't been called yet.
|
|
2148
2178
|
*/
|
|
2149
2179
|
get whenSent() {
|
|
2150
2180
|
if (!this._whenSent)
|
|
@@ -2410,7 +2440,7 @@ var RpcStream = class _RpcStream {
|
|
|
2410
2440
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
2411
2441
|
_sendAck(nextIndex, mustReset) {
|
|
2412
2442
|
this._ackSentUpTo = nextIndex;
|
|
2413
|
-
const conn = this.peer.
|
|
2443
|
+
const conn = this.peer.wireConnection;
|
|
2414
2444
|
if (conn) {
|
|
2415
2445
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
2416
2446
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -2423,7 +2453,7 @@ var RpcStream = class _RpcStream {
|
|
|
2423
2453
|
}
|
|
2424
2454
|
}
|
|
2425
2455
|
_sendAckEnd() {
|
|
2426
|
-
const conn = this.peer.
|
|
2456
|
+
const conn = this.peer.wireConnection;
|
|
2427
2457
|
if (conn) {
|
|
2428
2458
|
this.peer.hub.systemCallSender.ackEnd(
|
|
2429
2459
|
conn,
|
|
@@ -2787,6 +2817,8 @@ var RpcPeer = class {
|
|
|
2787
2817
|
/** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
|
|
2788
2818
|
* for the current connection. Useful for diagnostics. */
|
|
2789
2819
|
_lastKeepAliveAt = 0;
|
|
2820
|
+
/** When the current outage began; 0 until the peer has been connected at least once. */
|
|
2821
|
+
_disconnectedAt = 0;
|
|
2790
2822
|
/** Outbound keep-alive send period. Initialized from `hub.limits` at
|
|
2791
2823
|
* construction; set directly to override on a single peer. */
|
|
2792
2824
|
keepAlivePeriodMs;
|
|
@@ -2807,9 +2839,10 @@ var RpcPeer = class {
|
|
|
2807
2839
|
}
|
|
2808
2840
|
/** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
|
|
2809
2841
|
* the connect timeout while a call still awaits the wire, the run timeout
|
|
2810
|
-
* once it has been sent
|
|
2811
|
-
*
|
|
2812
|
-
*
|
|
2842
|
+
* once it has been sent, and the connect timeout again for any pending call while the
|
|
2843
|
+
* peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,
|
|
2844
|
+
* per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts
|
|
2845
|
+
* (queries) never time out here. */
|
|
2813
2846
|
_maintainOutboundCalls() {
|
|
2814
2847
|
const now = Date.now();
|
|
2815
2848
|
const canRunTimeout = this._isConnected && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);
|
|
@@ -2818,7 +2851,11 @@ var RpcPeer = class {
|
|
|
2818
2851
|
if (timeouts === void 0 || call.result.isCompleted)
|
|
2819
2852
|
continue;
|
|
2820
2853
|
let timedOut;
|
|
2821
|
-
if (
|
|
2854
|
+
if (!this._isConnected) {
|
|
2855
|
+
const since = Math.max(this._disconnectedAt, call.startedAt);
|
|
2856
|
+
if (now - since >= timeouts.connectTimeoutMs)
|
|
2857
|
+
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2858
|
+
} else if (call.sentAt === 0) {
|
|
2822
2859
|
if (now - call.startedAt >= timeouts.connectTimeoutMs)
|
|
2823
2860
|
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2824
2861
|
} else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)
|
|
@@ -2833,6 +2870,18 @@ var RpcPeer = class {
|
|
|
2833
2870
|
get connection() {
|
|
2834
2871
|
return this._connection;
|
|
2835
2872
|
}
|
|
2873
|
+
// The connection you may send a non-handshake message on, or `undefined`.
|
|
2874
|
+
// `_connection` is set before the socket even opens and stays set through
|
|
2875
|
+
// the handshake, and `RpcConnection` buffers pre-OPEN sends and flushes them
|
|
2876
|
+
// from `onopen` — so writing through it during Connecting or Handshaking puts
|
|
2877
|
+
// the frame on the wire ahead of $sys.Handshake and corrupts the remote's
|
|
2878
|
+
// handshake. `_isConnected` is the only flag false across both phases.
|
|
2879
|
+
// Port of C#'s `RpcPeer.Transport` (RpcPeer.cs:29-52), which hides the
|
|
2880
|
+
// transport for exactly this reason. $sys.Handshake itself must use
|
|
2881
|
+
// `connection` — it is the frame this gate exists to let through first.
|
|
2882
|
+
get wireConnection() {
|
|
2883
|
+
return this._isConnected ? this._connection : void 0;
|
|
2884
|
+
}
|
|
2836
2885
|
get connectionState() {
|
|
2837
2886
|
return this._connectionState;
|
|
2838
2887
|
}
|
|
@@ -2861,10 +2910,13 @@ var RpcPeer = class {
|
|
|
2861
2910
|
this._connectionState = value;
|
|
2862
2911
|
if (value === 3 /* Connected */) {
|
|
2863
2912
|
this._isConnected = true;
|
|
2913
|
+
this._disconnectedAt = 0;
|
|
2864
2914
|
this._armKeepAliveWatchdog();
|
|
2865
|
-
} else
|
|
2866
|
-
if (previousState === 3 /* Connected */)
|
|
2915
|
+
} else {
|
|
2916
|
+
if (previousState === 3 /* Connected */) {
|
|
2867
2917
|
this._isConnected = false;
|
|
2918
|
+
this._disconnectedAt = Date.now();
|
|
2919
|
+
}
|
|
2868
2920
|
this._disarmKeepAliveWatchdog();
|
|
2869
2921
|
}
|
|
2870
2922
|
this.connectionStateChanged.trigger(value);
|
|
@@ -2889,6 +2941,8 @@ var RpcPeer = class {
|
|
|
2889
2941
|
conn.closed.add((ev) => {
|
|
2890
2942
|
if (this._connection !== conn) return;
|
|
2891
2943
|
this._connection = void 0;
|
|
2944
|
+
if (this._isConnected)
|
|
2945
|
+
this._disconnectedAt = Date.now();
|
|
2892
2946
|
this._isConnected = false;
|
|
2893
2947
|
if (ev.reason)
|
|
2894
2948
|
infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);
|
|
@@ -2948,8 +3002,9 @@ var RpcPeer = class {
|
|
|
2948
3002
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2949
3003
|
outboundCall.result.reject((0, import_core7.cancellationError)("Call cancelled."));
|
|
2950
3004
|
outboundCall.onDisconnect();
|
|
2951
|
-
|
|
2952
|
-
|
|
3005
|
+
const conn = this.wireConnection;
|
|
3006
|
+
if (conn !== void 0)
|
|
3007
|
+
this.hub.systemCallSender.cancel(conn, this.serializationFormat, callId);
|
|
2953
3008
|
}
|
|
2954
3009
|
};
|
|
2955
3010
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2992,9 +3047,11 @@ var RpcPeer = class {
|
|
|
2992
3047
|
this._setConnectionState(0 /* Disconnected */);
|
|
2993
3048
|
infoLog?.log(`'${this.ref}': Stopped`);
|
|
2994
3049
|
}
|
|
2995
|
-
/** Send pre-serialized wire data through the current connection.
|
|
3050
|
+
/** Send pre-serialized wire data through the current connection. Callers are
|
|
3051
|
+
* already gated on `_isConnected`; the check is repeated here so the next one
|
|
3052
|
+
* cannot reintroduce a pre-handshake write. $sys.Handshake does not use this. */
|
|
2996
3053
|
_sendWireData(data) {
|
|
2997
|
-
if (this._connection === void 0) return;
|
|
3054
|
+
if (!this._isConnected || this._connection === void 0) return;
|
|
2998
3055
|
if (typeof data === "string") {
|
|
2999
3056
|
this._connection.send(data);
|
|
3000
3057
|
} else {
|
|
@@ -3077,9 +3134,10 @@ var RpcPeer = class {
|
|
|
3077
3134
|
const ref = stream.toRef(this);
|
|
3078
3135
|
const send = () => {
|
|
3079
3136
|
if (call2?.isCancelled === true) return;
|
|
3080
|
-
|
|
3137
|
+
const conn = this.wireConnection;
|
|
3138
|
+
if (conn !== void 0)
|
|
3081
3139
|
this.hub.systemCallSender.ok(
|
|
3082
|
-
|
|
3140
|
+
conn,
|
|
3083
3141
|
this.serializationFormat,
|
|
3084
3142
|
relatedId,
|
|
3085
3143
|
ref
|
|
@@ -3094,9 +3152,10 @@ var RpcPeer = class {
|
|
|
3094
3152
|
} else if (call2 !== void 0) {
|
|
3095
3153
|
const send = () => {
|
|
3096
3154
|
if (call2.isCancelled) return;
|
|
3097
|
-
|
|
3155
|
+
const conn = this.wireConnection;
|
|
3156
|
+
if (conn !== void 0)
|
|
3098
3157
|
this.hub.systemCallSender.ok(
|
|
3099
|
-
|
|
3158
|
+
conn,
|
|
3100
3159
|
this.serializationFormat,
|
|
3101
3160
|
relatedId,
|
|
3102
3161
|
result
|
|
@@ -3110,9 +3169,10 @@ var RpcPeer = class {
|
|
|
3110
3169
|
if (call2 !== void 0) {
|
|
3111
3170
|
const send = () => {
|
|
3112
3171
|
if (call2.isCancelled) return;
|
|
3113
|
-
|
|
3172
|
+
const conn = this.wireConnection;
|
|
3173
|
+
if (conn !== void 0)
|
|
3114
3174
|
this.hub.systemCallSender.error(
|
|
3115
|
-
|
|
3175
|
+
conn,
|
|
3116
3176
|
this.serializationFormat,
|
|
3117
3177
|
relatedId,
|
|
3118
3178
|
e
|
|
@@ -3145,8 +3205,9 @@ var RpcPeer = class {
|
|
|
3145
3205
|
* Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
|
|
3146
3206
|
_armKeepAliveWatchdog() {
|
|
3147
3207
|
this._keepAliveTimer ??= setInterval(() => {
|
|
3148
|
-
|
|
3149
|
-
|
|
3208
|
+
const conn = this.wireConnection;
|
|
3209
|
+
if (conn !== void 0) {
|
|
3210
|
+
this.hub.systemCallSender.keepAlive(conn, this.serializationFormat, [
|
|
3150
3211
|
...this.remoteObjects.keys()
|
|
3151
3212
|
]);
|
|
3152
3213
|
}
|
|
@@ -3294,9 +3355,26 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3294
3355
|
connectWith(conn, isPeerChanged = true) {
|
|
3295
3356
|
const sentCalls = this.outboundCalls.getSentCalls();
|
|
3296
3357
|
this.setupConnection(conn);
|
|
3358
|
+
const whenHandshake = isPeerChanged ? void 0 : this._pendingHandshake = new import_core7.PromiseSource();
|
|
3359
|
+
this.hub.systemCallSender.handshake(
|
|
3360
|
+
conn,
|
|
3361
|
+
this.serializationFormat,
|
|
3362
|
+
this.id,
|
|
3363
|
+
this.hub.hubId,
|
|
3364
|
+
this.nextOwnHandshakeIndex()
|
|
3365
|
+
);
|
|
3297
3366
|
this._isConnected = true;
|
|
3298
3367
|
this.connectionStateChanged.trigger(3 /* Connected */);
|
|
3299
|
-
|
|
3368
|
+
if (whenHandshake === void 0) {
|
|
3369
|
+
void this._reconnect(sentCalls, true);
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
const whenClosed = new import_core7.PromiseSource();
|
|
3373
|
+
conn.closed.add(() => whenClosed.resolve());
|
|
3374
|
+
void Promise.race([whenHandshake, whenClosed]).then(() => {
|
|
3375
|
+
this._pendingHandshake = void 0;
|
|
3376
|
+
return this._reconnect(sentCalls, false);
|
|
3377
|
+
});
|
|
3300
3378
|
}
|
|
3301
3379
|
/** Kick off the reconnect loop. Idempotent — subsequent calls are
|
|
3302
3380
|
* no-ops; await `peer.whenRunning` if you need the loop's completion
|
|
@@ -3614,6 +3692,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3614
3692
|
return 2 /* Changed */;
|
|
3615
3693
|
}
|
|
3616
3694
|
_onHandshakeReceived(handshake) {
|
|
3695
|
+
this._remoteHandshakeIndex = handshake.Index ?? 0;
|
|
3617
3696
|
this._pendingHandshake?.resolve(handshake);
|
|
3618
3697
|
}
|
|
3619
3698
|
close() {
|