@actuallab/rpc 14.4.3 → 14.4.14
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 +206 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.js +206 -140
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1193,13 +1193,17 @@ var RpcSystemCallHandler = class {
|
|
|
1193
1193
|
case RpcSystemCalls.keepAlive: {
|
|
1194
1194
|
peer.notifyKeepAliveReceived();
|
|
1195
1195
|
const keepAliveIds = args[0];
|
|
1196
|
-
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.
|
|
1196
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.wireConnection !== void 0) {
|
|
1197
1197
|
const unknownIds = [];
|
|
1198
1198
|
for (const id of keepAliveIds)
|
|
1199
1199
|
if (peer.sharedObjects.get(id) === void 0)
|
|
1200
1200
|
unknownIds.push(id);
|
|
1201
1201
|
if (unknownIds.length > 0)
|
|
1202
|
-
peer.hub.systemCallSender.disconnect(
|
|
1202
|
+
peer.hub.systemCallSender.disconnect(
|
|
1203
|
+
peer.wireConnection,
|
|
1204
|
+
peer.serializationFormat,
|
|
1205
|
+
unknownIds
|
|
1206
|
+
);
|
|
1203
1207
|
}
|
|
1204
1208
|
break;
|
|
1205
1209
|
}
|
|
@@ -1245,16 +1249,24 @@ var RpcSystemCallHandler = class {
|
|
|
1245
1249
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1246
1250
|
if (sender)
|
|
1247
1251
|
sender.onAck(args[0], args[1]);
|
|
1248
|
-
else if (peer.
|
|
1249
|
-
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
|
+
);
|
|
1250
1258
|
break;
|
|
1251
1259
|
}
|
|
1252
1260
|
case RpcSystemCalls.ackEnd: {
|
|
1253
1261
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1254
1262
|
if (sender)
|
|
1255
1263
|
sender.onAckEnd(args[0]);
|
|
1256
|
-
else if (peer.
|
|
1257
|
-
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
|
+
);
|
|
1258
1270
|
break;
|
|
1259
1271
|
}
|
|
1260
1272
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1283,12 +1295,13 @@ var RpcSystemCallHandler = class {
|
|
|
1283
1295
|
* with the same `RelatedId`.
|
|
1284
1296
|
*/
|
|
1285
1297
|
_handleReconnect(relatedId, args, peer) {
|
|
1286
|
-
|
|
1298
|
+
const conn = peer.wireConnection;
|
|
1299
|
+
if (conn === void 0) return;
|
|
1287
1300
|
const handshakeIndex = args[0];
|
|
1288
1301
|
if (typeof handshakeIndex !== "number" || handshakeIndex !== peer.ownHandshakeIndex) {
|
|
1289
1302
|
const got = typeof handshakeIndex === "number" ? handshakeIndex : `<${typeof handshakeIndex}>`;
|
|
1290
1303
|
peer.hub.systemCallSender.error(
|
|
1291
|
-
|
|
1304
|
+
conn,
|
|
1292
1305
|
peer.serializationFormat,
|
|
1293
1306
|
relatedId,
|
|
1294
1307
|
new Error(
|
|
@@ -1299,7 +1312,7 @@ var RpcSystemCallHandler = class {
|
|
|
1299
1312
|
}
|
|
1300
1313
|
if (!peer.tryClaimReconnect()) {
|
|
1301
1314
|
peer.hub.systemCallSender.error(
|
|
1302
|
-
|
|
1315
|
+
conn,
|
|
1303
1316
|
peer.serializationFormat,
|
|
1304
1317
|
relatedId,
|
|
1305
1318
|
new Error("TooLateToReconnect: this connection was already reconnected")
|
|
@@ -1330,7 +1343,7 @@ var RpcSystemCallHandler = class {
|
|
|
1330
1343
|
}
|
|
1331
1344
|
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1332
1345
|
const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1333
|
-
peer.hub.systemCallSender.ok(
|
|
1346
|
+
peer.hub.systemCallSender.ok(conn, peer.serializationFormat, relatedId, responseValue);
|
|
1334
1347
|
}
|
|
1335
1348
|
};
|
|
1336
1349
|
function polymorphicPayloadError(method, got, expected) {
|
|
@@ -1513,6 +1526,8 @@ var import_core6 = require("@actuallab/core");
|
|
|
1513
1526
|
// src/rpc-stream-sender.ts
|
|
1514
1527
|
var import_core5 = require("@actuallab/core");
|
|
1515
1528
|
var { warnLog: warnLog5 } = getLogs("RpcSharedStream");
|
|
1529
|
+
function _ignore() {
|
|
1530
|
+
}
|
|
1516
1531
|
var DEFAULT_ACK_PERIOD = 256;
|
|
1517
1532
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1518
1533
|
var RTT_WINDOW_MS = 2e4;
|
|
@@ -1552,7 +1567,11 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1552
1567
|
// -- Pump state --
|
|
1553
1568
|
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1554
1569
|
_nextIndex = 0;
|
|
1555
|
-
|
|
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;
|
|
1556
1575
|
_started = new import_core5.PromiseSource();
|
|
1557
1576
|
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1558
1577
|
_lastAckedIndex = 0;
|
|
@@ -1659,7 +1678,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1659
1678
|
this._sendDisconnect();
|
|
1660
1679
|
return;
|
|
1661
1680
|
}
|
|
1662
|
-
} else if (this.
|
|
1681
|
+
} else if (this._isCompleted) {
|
|
1663
1682
|
this._sendDisconnect();
|
|
1664
1683
|
return;
|
|
1665
1684
|
} else if (mustReset && !this.allowReconnect) {
|
|
@@ -1680,7 +1699,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1680
1699
|
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1681
1700
|
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1682
1701
|
_sendDisconnect() {
|
|
1683
|
-
const conn = this.peer.
|
|
1702
|
+
const conn = this.peer.wireConnection;
|
|
1684
1703
|
if (conn)
|
|
1685
1704
|
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1686
1705
|
this.disconnect();
|
|
@@ -1694,13 +1713,13 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1694
1713
|
*
|
|
1695
1714
|
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1696
1715
|
* the item is considered "sent" from the pump's perspective so the replay
|
|
1697
|
-
* buffer indexing stays consistent. If
|
|
1716
|
+
* buffer indexing stays consistent. If the peer isn't connected, the wire
|
|
1698
1717
|
* message is dropped; the item will be resent from the replay buffer
|
|
1699
1718
|
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1700
1719
|
*/
|
|
1701
1720
|
sendItem(item) {
|
|
1702
|
-
if (this.
|
|
1703
|
-
const conn = this.peer.
|
|
1721
|
+
if (this._isDisposed) return;
|
|
1722
|
+
const conn = this.peer.wireConnection;
|
|
1704
1723
|
if (conn) {
|
|
1705
1724
|
this.peer.hub.systemCallSender.item(
|
|
1706
1725
|
conn,
|
|
@@ -1717,8 +1736,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1717
1736
|
}
|
|
1718
1737
|
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1719
1738
|
sendBatch(items) {
|
|
1720
|
-
if (this.
|
|
1721
|
-
const conn = this.peer.
|
|
1739
|
+
if (this._isDisposed || items.length === 0) return;
|
|
1740
|
+
const conn = this.peer.wireConnection;
|
|
1722
1741
|
if (conn) {
|
|
1723
1742
|
this.peer.hub.systemCallSender.batch(
|
|
1724
1743
|
conn,
|
|
@@ -1730,18 +1749,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1730
1749
|
}
|
|
1731
1750
|
this._nextIndex += items.length;
|
|
1732
1751
|
}
|
|
1733
|
-
/** 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). */
|
|
1734
1756
|
sendEnd(error) {
|
|
1735
|
-
if (this.
|
|
1736
|
-
|
|
1737
|
-
const conn = this.peer.
|
|
1757
|
+
if (this._isDisposed) return;
|
|
1758
|
+
const index = this._nextIndex++;
|
|
1759
|
+
const conn = this.peer.wireConnection;
|
|
1738
1760
|
if (!conn) return;
|
|
1739
1761
|
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1740
1762
|
this.peer.hub.systemCallSender.end(
|
|
1741
1763
|
conn,
|
|
1742
1764
|
this.peer.serializationFormat,
|
|
1743
1765
|
this.id.localId,
|
|
1744
|
-
|
|
1766
|
+
index,
|
|
1745
1767
|
errorInfo
|
|
1746
1768
|
);
|
|
1747
1769
|
}
|
|
@@ -1753,13 +1775,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1753
1775
|
*/
|
|
1754
1776
|
async writeFrom(source) {
|
|
1755
1777
|
await this._started;
|
|
1756
|
-
if (this.
|
|
1778
|
+
if (this._isDisposed) return;
|
|
1757
1779
|
const iterator = source[Symbol.asyncIterator]();
|
|
1758
1780
|
this._iterator = iterator;
|
|
1759
1781
|
const state = { iteratorDone: false };
|
|
1760
1782
|
try {
|
|
1761
1783
|
await this._run(iterator, state);
|
|
1762
1784
|
} finally {
|
|
1785
|
+
this._isCompleted = true;
|
|
1763
1786
|
if (state.iteratorDone && this._iterator === iterator) {
|
|
1764
1787
|
this._iterator = null;
|
|
1765
1788
|
}
|
|
@@ -1772,7 +1795,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1772
1795
|
* With `bufferSize > ackAdvance` the local ring buffer holds more than the
|
|
1773
1796
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1774
1797
|
*/
|
|
1775
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition --
|
|
1798
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _isDisposed changes across awaits */
|
|
1776
1799
|
async _run(iterator, state) {
|
|
1777
1800
|
const buffer = new import_core5.RingBuffer(this.bufferSize + 1);
|
|
1778
1801
|
const isRealTime = this.isRealTime;
|
|
@@ -1805,7 +1828,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1805
1828
|
if (r === pending)
|
|
1806
1829
|
return false;
|
|
1807
1830
|
whenMovedNext = null;
|
|
1808
|
-
if (this.
|
|
1831
|
+
if (this._isDisposed) return false;
|
|
1809
1832
|
if (r.done) {
|
|
1810
1833
|
buffer.pushTail(_endItem);
|
|
1811
1834
|
isFullyBuffered = true;
|
|
@@ -1829,105 +1852,107 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1829
1852
|
return true;
|
|
1830
1853
|
}
|
|
1831
1854
|
};
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
await this._waitAckReady();
|
|
1836
|
-
if (this._ended) return;
|
|
1837
|
-
ack = this._tryProcessAcks();
|
|
1855
|
+
nextAck:
|
|
1856
|
+
while (true) {
|
|
1857
|
+
let ack = this._tryProcessAcks();
|
|
1838
1858
|
if (!ack) {
|
|
1839
|
-
|
|
1840
|
-
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
|
+
}
|
|
1841
1866
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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) {
|
|
1857
1889
|
state.iteratorDone = true;
|
|
1858
|
-
|
|
1859
|
-
item = {
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1890
|
+
isFullyBuffered = true;
|
|
1891
|
+
item = {
|
|
1892
|
+
kind: "error",
|
|
1893
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1894
|
+
};
|
|
1863
1895
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
isFullyBuffered
|
|
1867
|
-
item = {
|
|
1868
|
-
kind: "error",
|
|
1869
|
-
error: e instanceof Error ? e : new Error(String(e))
|
|
1870
|
-
};
|
|
1896
|
+
buffer.pushTail(item);
|
|
1897
|
+
fireBuffered();
|
|
1898
|
+
if (accepted || isFullyBuffered) break;
|
|
1871
1899
|
}
|
|
1872
|
-
buffer.pushTail(item);
|
|
1873
|
-
fireBuffered();
|
|
1874
|
-
if (accepted || isFullyBuffered) break;
|
|
1875
1900
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
bufferStart += shift;
|
|
1881
|
-
}
|
|
1882
|
-
if (this._nextIndex < bufferStart) {
|
|
1883
|
-
if (!this._ended)
|
|
1884
|
-
this.sendEnd(new Error("Stream position unavailable."));
|
|
1885
|
-
return;
|
|
1886
|
-
}
|
|
1887
|
-
let bufferIndex = this._nextIndex - bufferStart;
|
|
1888
|
-
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1889
|
-
while (this._nextIndex < maxIndex) {
|
|
1890
|
-
if (this._ended) return;
|
|
1891
|
-
if (isRealTime) {
|
|
1892
|
-
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1893
|
-
if (!await bufferNext(true, true))
|
|
1894
|
-
break;
|
|
1895
|
-
}
|
|
1896
|
-
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1897
|
-
await bufferNext(false, true);
|
|
1898
|
-
}
|
|
1899
|
-
if (ack.nextIndex > 0) {
|
|
1900
|
-
const result = _compactBufferedUnsentSuffix(
|
|
1901
|
-
buffer,
|
|
1902
|
-
bufferIndex,
|
|
1903
|
-
this.canSkipTo
|
|
1904
|
-
);
|
|
1905
|
-
bufferIndex = result.newIndex;
|
|
1906
|
-
if (result.skipped > 0)
|
|
1907
|
-
this._skipCount += result.skipped;
|
|
1908
|
-
}
|
|
1909
|
-
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;
|
|
1910
1905
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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;
|
|
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
|
+
}
|
|
1921
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);
|
|
1922
1954
|
}
|
|
1923
|
-
if (this._nextIndex >= maxIndex)
|
|
1924
|
-
break;
|
|
1925
|
-
if (isFullyBuffered) break;
|
|
1926
|
-
if (!buffer.hasRemainingCapacity) break;
|
|
1927
|
-
if (!isRealTime)
|
|
1928
|
-
await bufferNext(false, false);
|
|
1929
1955
|
}
|
|
1930
|
-
}
|
|
1931
1956
|
}
|
|
1932
1957
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1933
1958
|
get _hasPendingAck() {
|
|
@@ -1952,13 +1977,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1952
1977
|
}
|
|
1953
1978
|
return { nextIndex, mustReset };
|
|
1954
1979
|
}
|
|
1955
|
-
/** Wait until an ACK is pending (or
|
|
1980
|
+
/** Wait until an ACK is pending (or the sender is torn down). */
|
|
1956
1981
|
async _waitAckReady() {
|
|
1957
|
-
if (this._hasPendingAck || this.
|
|
1982
|
+
if (this._hasPendingAck || this._isDisposed)
|
|
1958
1983
|
return;
|
|
1959
|
-
this._whenAckReady
|
|
1960
|
-
await
|
|
1961
|
-
this._whenAckReady
|
|
1984
|
+
const whenAckReady = this._whenAckReady ??= new import_core5.PromiseSource();
|
|
1985
|
+
await whenAckReady;
|
|
1986
|
+
if (this._whenAckReady === whenAckReady)
|
|
1987
|
+
this._whenAckReady = null;
|
|
1962
1988
|
}
|
|
1963
1989
|
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1964
1990
|
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
@@ -1984,8 +2010,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1984
2010
|
reconnect() {
|
|
1985
2011
|
}
|
|
1986
2012
|
disconnect() {
|
|
1987
|
-
if (this.
|
|
1988
|
-
this.
|
|
2013
|
+
if (this._isDisposed) return;
|
|
2014
|
+
this._isDisposed = true;
|
|
1989
2015
|
this._abortController.abort();
|
|
1990
2016
|
if (!this._started.isCompleted) {
|
|
1991
2017
|
this._started.resolve();
|
|
@@ -2144,8 +2170,11 @@ var RpcStream = class _RpcStream {
|
|
|
2144
2170
|
}
|
|
2145
2171
|
}
|
|
2146
2172
|
/**
|
|
2147
|
-
* For local streams: resolves
|
|
2148
|
-
*
|
|
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.
|
|
2149
2178
|
*/
|
|
2150
2179
|
get whenSent() {
|
|
2151
2180
|
if (!this._whenSent)
|
|
@@ -2411,7 +2440,7 @@ var RpcStream = class _RpcStream {
|
|
|
2411
2440
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
2412
2441
|
_sendAck(nextIndex, mustReset) {
|
|
2413
2442
|
this._ackSentUpTo = nextIndex;
|
|
2414
|
-
const conn = this.peer.
|
|
2443
|
+
const conn = this.peer.wireConnection;
|
|
2415
2444
|
if (conn) {
|
|
2416
2445
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
2417
2446
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -2424,7 +2453,7 @@ var RpcStream = class _RpcStream {
|
|
|
2424
2453
|
}
|
|
2425
2454
|
}
|
|
2426
2455
|
_sendAckEnd() {
|
|
2427
|
-
const conn = this.peer.
|
|
2456
|
+
const conn = this.peer.wireConnection;
|
|
2428
2457
|
if (conn) {
|
|
2429
2458
|
this.peer.hub.systemCallSender.ackEnd(
|
|
2430
2459
|
conn,
|
|
@@ -2841,6 +2870,18 @@ var RpcPeer = class {
|
|
|
2841
2870
|
get connection() {
|
|
2842
2871
|
return this._connection;
|
|
2843
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
|
+
}
|
|
2844
2885
|
get connectionState() {
|
|
2845
2886
|
return this._connectionState;
|
|
2846
2887
|
}
|
|
@@ -2871,7 +2912,7 @@ var RpcPeer = class {
|
|
|
2871
2912
|
this._isConnected = true;
|
|
2872
2913
|
this._disconnectedAt = 0;
|
|
2873
2914
|
this._armKeepAliveWatchdog();
|
|
2874
|
-
} else
|
|
2915
|
+
} else {
|
|
2875
2916
|
if (previousState === 3 /* Connected */) {
|
|
2876
2917
|
this._isConnected = false;
|
|
2877
2918
|
this._disconnectedAt = Date.now();
|
|
@@ -2961,8 +3002,9 @@ var RpcPeer = class {
|
|
|
2961
3002
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2962
3003
|
outboundCall.result.reject((0, import_core7.cancellationError)("Call cancelled."));
|
|
2963
3004
|
outboundCall.onDisconnect();
|
|
2964
|
-
|
|
2965
|
-
|
|
3005
|
+
const conn = this.wireConnection;
|
|
3006
|
+
if (conn !== void 0)
|
|
3007
|
+
this.hub.systemCallSender.cancel(conn, this.serializationFormat, callId);
|
|
2966
3008
|
}
|
|
2967
3009
|
};
|
|
2968
3010
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -3005,9 +3047,11 @@ var RpcPeer = class {
|
|
|
3005
3047
|
this._setConnectionState(0 /* Disconnected */);
|
|
3006
3048
|
infoLog?.log(`'${this.ref}': Stopped`);
|
|
3007
3049
|
}
|
|
3008
|
-
/** 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. */
|
|
3009
3053
|
_sendWireData(data) {
|
|
3010
|
-
if (this._connection === void 0) return;
|
|
3054
|
+
if (!this._isConnected || this._connection === void 0) return;
|
|
3011
3055
|
if (typeof data === "string") {
|
|
3012
3056
|
this._connection.send(data);
|
|
3013
3057
|
} else {
|
|
@@ -3090,9 +3134,10 @@ var RpcPeer = class {
|
|
|
3090
3134
|
const ref = stream.toRef(this);
|
|
3091
3135
|
const send = () => {
|
|
3092
3136
|
if (call2?.isCancelled === true) return;
|
|
3093
|
-
|
|
3137
|
+
const conn = this.wireConnection;
|
|
3138
|
+
if (conn !== void 0)
|
|
3094
3139
|
this.hub.systemCallSender.ok(
|
|
3095
|
-
|
|
3140
|
+
conn,
|
|
3096
3141
|
this.serializationFormat,
|
|
3097
3142
|
relatedId,
|
|
3098
3143
|
ref
|
|
@@ -3107,9 +3152,10 @@ var RpcPeer = class {
|
|
|
3107
3152
|
} else if (call2 !== void 0) {
|
|
3108
3153
|
const send = () => {
|
|
3109
3154
|
if (call2.isCancelled) return;
|
|
3110
|
-
|
|
3155
|
+
const conn = this.wireConnection;
|
|
3156
|
+
if (conn !== void 0)
|
|
3111
3157
|
this.hub.systemCallSender.ok(
|
|
3112
|
-
|
|
3158
|
+
conn,
|
|
3113
3159
|
this.serializationFormat,
|
|
3114
3160
|
relatedId,
|
|
3115
3161
|
result
|
|
@@ -3123,9 +3169,10 @@ var RpcPeer = class {
|
|
|
3123
3169
|
if (call2 !== void 0) {
|
|
3124
3170
|
const send = () => {
|
|
3125
3171
|
if (call2.isCancelled) return;
|
|
3126
|
-
|
|
3172
|
+
const conn = this.wireConnection;
|
|
3173
|
+
if (conn !== void 0)
|
|
3127
3174
|
this.hub.systemCallSender.error(
|
|
3128
|
-
|
|
3175
|
+
conn,
|
|
3129
3176
|
this.serializationFormat,
|
|
3130
3177
|
relatedId,
|
|
3131
3178
|
e
|
|
@@ -3158,8 +3205,9 @@ var RpcPeer = class {
|
|
|
3158
3205
|
* Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
|
|
3159
3206
|
_armKeepAliveWatchdog() {
|
|
3160
3207
|
this._keepAliveTimer ??= setInterval(() => {
|
|
3161
|
-
|
|
3162
|
-
|
|
3208
|
+
const conn = this.wireConnection;
|
|
3209
|
+
if (conn !== void 0) {
|
|
3210
|
+
this.hub.systemCallSender.keepAlive(conn, this.serializationFormat, [
|
|
3163
3211
|
...this.remoteObjects.keys()
|
|
3164
3212
|
]);
|
|
3165
3213
|
}
|
|
@@ -3307,9 +3355,26 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3307
3355
|
connectWith(conn, isPeerChanged = true) {
|
|
3308
3356
|
const sentCalls = this.outboundCalls.getSentCalls();
|
|
3309
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
|
+
);
|
|
3310
3366
|
this._isConnected = true;
|
|
3311
3367
|
this.connectionStateChanged.trigger(3 /* Connected */);
|
|
3312
|
-
|
|
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
|
+
});
|
|
3313
3378
|
}
|
|
3314
3379
|
/** Kick off the reconnect loop. Idempotent — subsequent calls are
|
|
3315
3380
|
* no-ops; await `peer.whenRunning` if you need the loop's completion
|
|
@@ -3627,6 +3692,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3627
3692
|
return 2 /* Changed */;
|
|
3628
3693
|
}
|
|
3629
3694
|
_onHandshakeReceived(handshake) {
|
|
3695
|
+
this._remoteHandshakeIndex = handshake.Index ?? 0;
|
|
3630
3696
|
this._pendingHandshake?.resolve(handshake);
|
|
3631
3697
|
}
|
|
3632
3698
|
close() {
|