@actuallab/rpc 14.4.3 → 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 +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.js
CHANGED
|
@@ -1079,13 +1079,17 @@ var RpcSystemCallHandler = class {
|
|
|
1079
1079
|
case RpcSystemCalls.keepAlive: {
|
|
1080
1080
|
peer.notifyKeepAliveReceived();
|
|
1081
1081
|
const keepAliveIds = args[0];
|
|
1082
|
-
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.
|
|
1082
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.wireConnection !== void 0) {
|
|
1083
1083
|
const unknownIds = [];
|
|
1084
1084
|
for (const id of keepAliveIds)
|
|
1085
1085
|
if (peer.sharedObjects.get(id) === void 0)
|
|
1086
1086
|
unknownIds.push(id);
|
|
1087
1087
|
if (unknownIds.length > 0)
|
|
1088
|
-
peer.hub.systemCallSender.disconnect(
|
|
1088
|
+
peer.hub.systemCallSender.disconnect(
|
|
1089
|
+
peer.wireConnection,
|
|
1090
|
+
peer.serializationFormat,
|
|
1091
|
+
unknownIds
|
|
1092
|
+
);
|
|
1089
1093
|
}
|
|
1090
1094
|
break;
|
|
1091
1095
|
}
|
|
@@ -1131,16 +1135,24 @@ var RpcSystemCallHandler = class {
|
|
|
1131
1135
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1132
1136
|
if (sender)
|
|
1133
1137
|
sender.onAck(args[0], args[1]);
|
|
1134
|
-
else if (peer.
|
|
1135
|
-
peer.hub.systemCallSender.disconnect(
|
|
1138
|
+
else if (peer.wireConnection !== void 0)
|
|
1139
|
+
peer.hub.systemCallSender.disconnect(
|
|
1140
|
+
peer.wireConnection,
|
|
1141
|
+
peer.serializationFormat,
|
|
1142
|
+
[relatedId]
|
|
1143
|
+
);
|
|
1136
1144
|
break;
|
|
1137
1145
|
}
|
|
1138
1146
|
case RpcSystemCalls.ackEnd: {
|
|
1139
1147
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1140
1148
|
if (sender)
|
|
1141
1149
|
sender.onAckEnd(args[0]);
|
|
1142
|
-
else if (peer.
|
|
1143
|
-
peer.hub.systemCallSender.disconnect(
|
|
1150
|
+
else if (peer.wireConnection !== void 0)
|
|
1151
|
+
peer.hub.systemCallSender.disconnect(
|
|
1152
|
+
peer.wireConnection,
|
|
1153
|
+
peer.serializationFormat,
|
|
1154
|
+
[relatedId]
|
|
1155
|
+
);
|
|
1144
1156
|
break;
|
|
1145
1157
|
}
|
|
1146
1158
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1169,12 +1181,13 @@ var RpcSystemCallHandler = class {
|
|
|
1169
1181
|
* with the same `RelatedId`.
|
|
1170
1182
|
*/
|
|
1171
1183
|
_handleReconnect(relatedId, args, peer) {
|
|
1172
|
-
|
|
1184
|
+
const conn = peer.wireConnection;
|
|
1185
|
+
if (conn === void 0) return;
|
|
1173
1186
|
const handshakeIndex = args[0];
|
|
1174
1187
|
if (typeof handshakeIndex !== "number" || handshakeIndex !== peer.ownHandshakeIndex) {
|
|
1175
1188
|
const got = typeof handshakeIndex === "number" ? handshakeIndex : `<${typeof handshakeIndex}>`;
|
|
1176
1189
|
peer.hub.systemCallSender.error(
|
|
1177
|
-
|
|
1190
|
+
conn,
|
|
1178
1191
|
peer.serializationFormat,
|
|
1179
1192
|
relatedId,
|
|
1180
1193
|
new Error(
|
|
@@ -1185,7 +1198,7 @@ var RpcSystemCallHandler = class {
|
|
|
1185
1198
|
}
|
|
1186
1199
|
if (!peer.tryClaimReconnect()) {
|
|
1187
1200
|
peer.hub.systemCallSender.error(
|
|
1188
|
-
|
|
1201
|
+
conn,
|
|
1189
1202
|
peer.serializationFormat,
|
|
1190
1203
|
relatedId,
|
|
1191
1204
|
new Error("TooLateToReconnect: this connection was already reconnected")
|
|
@@ -1216,7 +1229,7 @@ var RpcSystemCallHandler = class {
|
|
|
1216
1229
|
}
|
|
1217
1230
|
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1218
1231
|
const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1219
|
-
peer.hub.systemCallSender.ok(
|
|
1232
|
+
peer.hub.systemCallSender.ok(conn, peer.serializationFormat, relatedId, responseValue);
|
|
1220
1233
|
}
|
|
1221
1234
|
};
|
|
1222
1235
|
function polymorphicPayloadError(method, got, expected) {
|
|
@@ -1399,6 +1412,8 @@ import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
|
1399
1412
|
// src/rpc-stream-sender.ts
|
|
1400
1413
|
import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
|
|
1401
1414
|
var { warnLog: warnLog5 } = getLogs("RpcSharedStream");
|
|
1415
|
+
function _ignore() {
|
|
1416
|
+
}
|
|
1402
1417
|
var DEFAULT_ACK_PERIOD = 256;
|
|
1403
1418
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1404
1419
|
var RTT_WINDOW_MS = 2e4;
|
|
@@ -1438,7 +1453,11 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1438
1453
|
// -- Pump state --
|
|
1439
1454
|
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1440
1455
|
_nextIndex = 0;
|
|
1441
|
-
|
|
1456
|
+
/** Torn down — `disconnect()` ran. .NET's counterpart is disposal; note it
|
|
1457
|
+
* does NOT mean "an End was sent", which .NET does not track at all. */
|
|
1458
|
+
_isDisposed = false;
|
|
1459
|
+
/** The pump has returned. .NET's `WhenRunning.IsCompleted`. */
|
|
1460
|
+
_isCompleted = false;
|
|
1442
1461
|
_started = new PromiseSource3();
|
|
1443
1462
|
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1444
1463
|
_lastAckedIndex = 0;
|
|
@@ -1545,7 +1564,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1545
1564
|
this._sendDisconnect();
|
|
1546
1565
|
return;
|
|
1547
1566
|
}
|
|
1548
|
-
} else if (this.
|
|
1567
|
+
} else if (this._isCompleted) {
|
|
1549
1568
|
this._sendDisconnect();
|
|
1550
1569
|
return;
|
|
1551
1570
|
} else if (mustReset && !this.allowReconnect) {
|
|
@@ -1566,7 +1585,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1566
1585
|
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1567
1586
|
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1568
1587
|
_sendDisconnect() {
|
|
1569
|
-
const conn = this.peer.
|
|
1588
|
+
const conn = this.peer.wireConnection;
|
|
1570
1589
|
if (conn)
|
|
1571
1590
|
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1572
1591
|
this.disconnect();
|
|
@@ -1580,13 +1599,13 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1580
1599
|
*
|
|
1581
1600
|
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1582
1601
|
* the item is considered "sent" from the pump's perspective so the replay
|
|
1583
|
-
* buffer indexing stays consistent. If
|
|
1602
|
+
* buffer indexing stays consistent. If the peer isn't connected, the wire
|
|
1584
1603
|
* message is dropped; the item will be resent from the replay buffer
|
|
1585
1604
|
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1586
1605
|
*/
|
|
1587
1606
|
sendItem(item) {
|
|
1588
|
-
if (this.
|
|
1589
|
-
const conn = this.peer.
|
|
1607
|
+
if (this._isDisposed) return;
|
|
1608
|
+
const conn = this.peer.wireConnection;
|
|
1590
1609
|
if (conn) {
|
|
1591
1610
|
this.peer.hub.systemCallSender.item(
|
|
1592
1611
|
conn,
|
|
@@ -1603,8 +1622,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1603
1622
|
}
|
|
1604
1623
|
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1605
1624
|
sendBatch(items) {
|
|
1606
|
-
if (this.
|
|
1607
|
-
const conn = this.peer.
|
|
1625
|
+
if (this._isDisposed || items.length === 0) return;
|
|
1626
|
+
const conn = this.peer.wireConnection;
|
|
1608
1627
|
if (conn) {
|
|
1609
1628
|
this.peer.hub.systemCallSender.batch(
|
|
1610
1629
|
conn,
|
|
@@ -1616,18 +1635,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1616
1635
|
}
|
|
1617
1636
|
this._nextIndex += items.length;
|
|
1618
1637
|
}
|
|
1619
|
-
/** Signal stream completion to the client.
|
|
1638
|
+
/** Signal stream completion to the client. Re-sendable on purpose: if the
|
|
1639
|
+
* peer was not connected the frame is dropped, and the End stays in the
|
|
1640
|
+
* replay buffer for the next reset ack — .NET has no "already ended" latch
|
|
1641
|
+
* (the End is an ordinary buffer entry, RpcSharedStream.cs:185/239). */
|
|
1620
1642
|
sendEnd(error) {
|
|
1621
|
-
if (this.
|
|
1622
|
-
|
|
1623
|
-
const conn = this.peer.
|
|
1643
|
+
if (this._isDisposed) return;
|
|
1644
|
+
const index = this._nextIndex++;
|
|
1645
|
+
const conn = this.peer.wireConnection;
|
|
1624
1646
|
if (!conn) return;
|
|
1625
1647
|
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1626
1648
|
this.peer.hub.systemCallSender.end(
|
|
1627
1649
|
conn,
|
|
1628
1650
|
this.peer.serializationFormat,
|
|
1629
1651
|
this.id.localId,
|
|
1630
|
-
|
|
1652
|
+
index,
|
|
1631
1653
|
errorInfo
|
|
1632
1654
|
);
|
|
1633
1655
|
}
|
|
@@ -1639,13 +1661,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1639
1661
|
*/
|
|
1640
1662
|
async writeFrom(source) {
|
|
1641
1663
|
await this._started;
|
|
1642
|
-
if (this.
|
|
1664
|
+
if (this._isDisposed) return;
|
|
1643
1665
|
const iterator = source[Symbol.asyncIterator]();
|
|
1644
1666
|
this._iterator = iterator;
|
|
1645
1667
|
const state = { iteratorDone: false };
|
|
1646
1668
|
try {
|
|
1647
1669
|
await this._run(iterator, state);
|
|
1648
1670
|
} finally {
|
|
1671
|
+
this._isCompleted = true;
|
|
1649
1672
|
if (state.iteratorDone && this._iterator === iterator) {
|
|
1650
1673
|
this._iterator = null;
|
|
1651
1674
|
}
|
|
@@ -1658,7 +1681,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1658
1681
|
* With `bufferSize > ackAdvance` the local ring buffer holds more than the
|
|
1659
1682
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1660
1683
|
*/
|
|
1661
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition --
|
|
1684
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _isDisposed changes across awaits */
|
|
1662
1685
|
async _run(iterator, state) {
|
|
1663
1686
|
const buffer = new RingBuffer(this.bufferSize + 1);
|
|
1664
1687
|
const isRealTime = this.isRealTime;
|
|
@@ -1691,7 +1714,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1691
1714
|
if (r === pending)
|
|
1692
1715
|
return false;
|
|
1693
1716
|
whenMovedNext = null;
|
|
1694
|
-
if (this.
|
|
1717
|
+
if (this._isDisposed) return false;
|
|
1695
1718
|
if (r.done) {
|
|
1696
1719
|
buffer.pushTail(_endItem);
|
|
1697
1720
|
isFullyBuffered = true;
|
|
@@ -1715,105 +1738,107 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1715
1738
|
return true;
|
|
1716
1739
|
}
|
|
1717
1740
|
};
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
await this._waitAckReady();
|
|
1722
|
-
if (this._ended) return;
|
|
1723
|
-
ack = this._tryProcessAcks();
|
|
1741
|
+
nextAck:
|
|
1742
|
+
while (true) {
|
|
1743
|
+
let ack = this._tryProcessAcks();
|
|
1724
1744
|
if (!ack) {
|
|
1725
|
-
|
|
1726
|
-
return;
|
|
1745
|
+
await this._waitAckReady();
|
|
1746
|
+
if (this._isDisposed) return;
|
|
1747
|
+
ack = this._tryProcessAcks();
|
|
1748
|
+
if (!ack) {
|
|
1749
|
+
warnLog5?.log("Something is off: couldn't read an acknowledgement");
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1727
1752
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1753
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1754
|
+
if (isRealTime && ack.mustReset && !isFullyBuffered) {
|
|
1755
|
+
buffer.clear();
|
|
1756
|
+
bufferStart = this._nextIndex;
|
|
1757
|
+
while (true) {
|
|
1758
|
+
if (this._isDisposed) return;
|
|
1759
|
+
let item;
|
|
1760
|
+
let accepted = false;
|
|
1761
|
+
try {
|
|
1762
|
+
const r = await readNext();
|
|
1763
|
+
if (this._isDisposed) return;
|
|
1764
|
+
if (r.done) {
|
|
1765
|
+
item = _endItem;
|
|
1766
|
+
isFullyBuffered = true;
|
|
1767
|
+
state.iteratorDone = true;
|
|
1768
|
+
} else if (this.canSkipTo(r.value)) {
|
|
1769
|
+
item = { kind: "value", value: r.value };
|
|
1770
|
+
accepted = true;
|
|
1771
|
+
} else {
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
} catch (e) {
|
|
1743
1775
|
state.iteratorDone = true;
|
|
1744
|
-
|
|
1745
|
-
item = {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1776
|
+
isFullyBuffered = true;
|
|
1777
|
+
item = {
|
|
1778
|
+
kind: "error",
|
|
1779
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1780
|
+
};
|
|
1749
1781
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
isFullyBuffered
|
|
1753
|
-
item = {
|
|
1754
|
-
kind: "error",
|
|
1755
|
-
error: e instanceof Error ? e : new Error(String(e))
|
|
1756
|
-
};
|
|
1782
|
+
buffer.pushTail(item);
|
|
1783
|
+
fireBuffered();
|
|
1784
|
+
if (accepted || isFullyBuffered) break;
|
|
1757
1785
|
}
|
|
1758
|
-
buffer.pushTail(item);
|
|
1759
|
-
fireBuffered();
|
|
1760
|
-
if (accepted || isFullyBuffered) break;
|
|
1761
1786
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
bufferStart += shift;
|
|
1767
|
-
}
|
|
1768
|
-
if (this._nextIndex < bufferStart) {
|
|
1769
|
-
if (!this._ended)
|
|
1770
|
-
this.sendEnd(new Error("Stream position unavailable."));
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
|
-
let bufferIndex = this._nextIndex - bufferStart;
|
|
1774
|
-
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1775
|
-
while (this._nextIndex < maxIndex) {
|
|
1776
|
-
if (this._ended) return;
|
|
1777
|
-
if (isRealTime) {
|
|
1778
|
-
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1779
|
-
if (!await bufferNext(true, true))
|
|
1780
|
-
break;
|
|
1781
|
-
}
|
|
1782
|
-
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1783
|
-
await bufferNext(false, true);
|
|
1784
|
-
}
|
|
1785
|
-
if (ack.nextIndex > 0) {
|
|
1786
|
-
const result = _compactBufferedUnsentSuffix(
|
|
1787
|
-
buffer,
|
|
1788
|
-
bufferIndex,
|
|
1789
|
-
this.canSkipTo
|
|
1790
|
-
);
|
|
1791
|
-
bufferIndex = result.newIndex;
|
|
1792
|
-
if (result.skipped > 0)
|
|
1793
|
-
this._skipCount += result.skipped;
|
|
1794
|
-
}
|
|
1795
|
-
if (this._hasPendingAck) break;
|
|
1787
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1788
|
+
if (shift > 0) {
|
|
1789
|
+
buffer.moveHead(shift);
|
|
1790
|
+
bufferStart += shift;
|
|
1796
1791
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1792
|
+
if (this._nextIndex < bufferStart) {
|
|
1793
|
+
if (!this._isDisposed)
|
|
1794
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1798
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1799
|
+
while (this._nextIndex < maxIndex) {
|
|
1800
|
+
if (this._isDisposed) return;
|
|
1801
|
+
if (isRealTime) {
|
|
1802
|
+
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1803
|
+
if (!await bufferNext(true, true))
|
|
1804
|
+
break;
|
|
1805
|
+
}
|
|
1806
|
+
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1807
|
+
whenMovedNext ??= iterator.next();
|
|
1808
|
+
const whenAckReady = this._whenAckReady ??= new PromiseSource3();
|
|
1809
|
+
await Promise.race([whenMovedNext.then(_ignore, _ignore), whenAckReady]);
|
|
1810
|
+
if (!this._hasPendingAck)
|
|
1811
|
+
await bufferNext(false, true);
|
|
1812
|
+
}
|
|
1813
|
+
if (ack.nextIndex > 0) {
|
|
1814
|
+
const result = _compactBufferedUnsentSuffix(
|
|
1815
|
+
buffer,
|
|
1816
|
+
bufferIndex,
|
|
1817
|
+
this.canSkipTo
|
|
1818
|
+
);
|
|
1819
|
+
bufferIndex = result.newIndex;
|
|
1820
|
+
if (result.skipped > 0)
|
|
1821
|
+
this._skipCount += result.skipped;
|
|
1822
|
+
}
|
|
1823
|
+
if (this._hasPendingAck) break;
|
|
1824
|
+
}
|
|
1825
|
+
while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {
|
|
1826
|
+
const item = buffer.get(bufferIndex++);
|
|
1827
|
+
if (item.kind === "value") {
|
|
1828
|
+
this.sendItem(item.value);
|
|
1829
|
+
} else {
|
|
1830
|
+
this.sendEnd(item.kind === "error" ? item.error : void 0);
|
|
1831
|
+
continue nextAck;
|
|
1832
|
+
}
|
|
1807
1833
|
}
|
|
1834
|
+
if (this._nextIndex >= maxIndex)
|
|
1835
|
+
break;
|
|
1836
|
+
if (isFullyBuffered) break;
|
|
1837
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1838
|
+
if (!isRealTime)
|
|
1839
|
+
await bufferNext(false, false);
|
|
1808
1840
|
}
|
|
1809
|
-
if (this._nextIndex >= maxIndex)
|
|
1810
|
-
break;
|
|
1811
|
-
if (isFullyBuffered) break;
|
|
1812
|
-
if (!buffer.hasRemainingCapacity) break;
|
|
1813
|
-
if (!isRealTime)
|
|
1814
|
-
await bufferNext(false, false);
|
|
1815
1841
|
}
|
|
1816
|
-
}
|
|
1817
1842
|
}
|
|
1818
1843
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1819
1844
|
get _hasPendingAck() {
|
|
@@ -1838,13 +1863,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1838
1863
|
}
|
|
1839
1864
|
return { nextIndex, mustReset };
|
|
1840
1865
|
}
|
|
1841
|
-
/** Wait until an ACK is pending (or
|
|
1866
|
+
/** Wait until an ACK is pending (or the sender is torn down). */
|
|
1842
1867
|
async _waitAckReady() {
|
|
1843
|
-
if (this._hasPendingAck || this.
|
|
1868
|
+
if (this._hasPendingAck || this._isDisposed)
|
|
1844
1869
|
return;
|
|
1845
|
-
this._whenAckReady
|
|
1846
|
-
await
|
|
1847
|
-
this._whenAckReady
|
|
1870
|
+
const whenAckReady = this._whenAckReady ??= new PromiseSource3();
|
|
1871
|
+
await whenAckReady;
|
|
1872
|
+
if (this._whenAckReady === whenAckReady)
|
|
1873
|
+
this._whenAckReady = null;
|
|
1848
1874
|
}
|
|
1849
1875
|
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1850
1876
|
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
@@ -1870,8 +1896,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1870
1896
|
reconnect() {
|
|
1871
1897
|
}
|
|
1872
1898
|
disconnect() {
|
|
1873
|
-
if (this.
|
|
1874
|
-
this.
|
|
1899
|
+
if (this._isDisposed) return;
|
|
1900
|
+
this._isDisposed = true;
|
|
1875
1901
|
this._abortController.abort();
|
|
1876
1902
|
if (!this._started.isCompleted) {
|
|
1877
1903
|
this._started.resolve();
|
|
@@ -2030,8 +2056,11 @@ var RpcStream = class _RpcStream {
|
|
|
2030
2056
|
}
|
|
2031
2057
|
}
|
|
2032
2058
|
/**
|
|
2033
|
-
* For local streams: resolves
|
|
2034
|
-
*
|
|
2059
|
+
* For local streams: resolves once the consumer acknowledges the end
|
|
2060
|
+
* ($sys.AckEnd) or the sender is disconnected — NOT when the source drains,
|
|
2061
|
+
* since the sender keeps serving acks after End so a reconnect can replay it.
|
|
2062
|
+
* A source error reaches the consumer as $sys.End(error) instead of rejecting
|
|
2063
|
+
* this. Throws if `toRef()` hasn't been called yet.
|
|
2035
2064
|
*/
|
|
2036
2065
|
get whenSent() {
|
|
2037
2066
|
if (!this._whenSent)
|
|
@@ -2297,7 +2326,7 @@ var RpcStream = class _RpcStream {
|
|
|
2297
2326
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
2298
2327
|
_sendAck(nextIndex, mustReset) {
|
|
2299
2328
|
this._ackSentUpTo = nextIndex;
|
|
2300
|
-
const conn = this.peer.
|
|
2329
|
+
const conn = this.peer.wireConnection;
|
|
2301
2330
|
if (conn) {
|
|
2302
2331
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
2303
2332
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -2310,7 +2339,7 @@ var RpcStream = class _RpcStream {
|
|
|
2310
2339
|
}
|
|
2311
2340
|
}
|
|
2312
2341
|
_sendAckEnd() {
|
|
2313
|
-
const conn = this.peer.
|
|
2342
|
+
const conn = this.peer.wireConnection;
|
|
2314
2343
|
if (conn) {
|
|
2315
2344
|
this.peer.hub.systemCallSender.ackEnd(
|
|
2316
2345
|
conn,
|
|
@@ -2727,6 +2756,18 @@ var RpcPeer = class {
|
|
|
2727
2756
|
get connection() {
|
|
2728
2757
|
return this._connection;
|
|
2729
2758
|
}
|
|
2759
|
+
// The connection you may send a non-handshake message on, or `undefined`.
|
|
2760
|
+
// `_connection` is set before the socket even opens and stays set through
|
|
2761
|
+
// the handshake, and `RpcConnection` buffers pre-OPEN sends and flushes them
|
|
2762
|
+
// from `onopen` — so writing through it during Connecting or Handshaking puts
|
|
2763
|
+
// the frame on the wire ahead of $sys.Handshake and corrupts the remote's
|
|
2764
|
+
// handshake. `_isConnected` is the only flag false across both phases.
|
|
2765
|
+
// Port of C#'s `RpcPeer.Transport` (RpcPeer.cs:29-52), which hides the
|
|
2766
|
+
// transport for exactly this reason. $sys.Handshake itself must use
|
|
2767
|
+
// `connection` — it is the frame this gate exists to let through first.
|
|
2768
|
+
get wireConnection() {
|
|
2769
|
+
return this._isConnected ? this._connection : void 0;
|
|
2770
|
+
}
|
|
2730
2771
|
get connectionState() {
|
|
2731
2772
|
return this._connectionState;
|
|
2732
2773
|
}
|
|
@@ -2757,7 +2798,7 @@ var RpcPeer = class {
|
|
|
2757
2798
|
this._isConnected = true;
|
|
2758
2799
|
this._disconnectedAt = 0;
|
|
2759
2800
|
this._armKeepAliveWatchdog();
|
|
2760
|
-
} else
|
|
2801
|
+
} else {
|
|
2761
2802
|
if (previousState === 3 /* Connected */) {
|
|
2762
2803
|
this._isConnected = false;
|
|
2763
2804
|
this._disconnectedAt = Date.now();
|
|
@@ -2847,8 +2888,9 @@ var RpcPeer = class {
|
|
|
2847
2888
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2848
2889
|
outboundCall.result.reject(cancellationError("Call cancelled."));
|
|
2849
2890
|
outboundCall.onDisconnect();
|
|
2850
|
-
|
|
2851
|
-
|
|
2891
|
+
const conn = this.wireConnection;
|
|
2892
|
+
if (conn !== void 0)
|
|
2893
|
+
this.hub.systemCallSender.cancel(conn, this.serializationFormat, callId);
|
|
2852
2894
|
}
|
|
2853
2895
|
};
|
|
2854
2896
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2891,9 +2933,11 @@ var RpcPeer = class {
|
|
|
2891
2933
|
this._setConnectionState(0 /* Disconnected */);
|
|
2892
2934
|
infoLog?.log(`'${this.ref}': Stopped`);
|
|
2893
2935
|
}
|
|
2894
|
-
/** Send pre-serialized wire data through the current connection.
|
|
2936
|
+
/** Send pre-serialized wire data through the current connection. Callers are
|
|
2937
|
+
* already gated on `_isConnected`; the check is repeated here so the next one
|
|
2938
|
+
* cannot reintroduce a pre-handshake write. $sys.Handshake does not use this. */
|
|
2895
2939
|
_sendWireData(data) {
|
|
2896
|
-
if (this._connection === void 0) return;
|
|
2940
|
+
if (!this._isConnected || this._connection === void 0) return;
|
|
2897
2941
|
if (typeof data === "string") {
|
|
2898
2942
|
this._connection.send(data);
|
|
2899
2943
|
} else {
|
|
@@ -2976,9 +3020,10 @@ var RpcPeer = class {
|
|
|
2976
3020
|
const ref = stream.toRef(this);
|
|
2977
3021
|
const send = () => {
|
|
2978
3022
|
if (call2?.isCancelled === true) return;
|
|
2979
|
-
|
|
3023
|
+
const conn = this.wireConnection;
|
|
3024
|
+
if (conn !== void 0)
|
|
2980
3025
|
this.hub.systemCallSender.ok(
|
|
2981
|
-
|
|
3026
|
+
conn,
|
|
2982
3027
|
this.serializationFormat,
|
|
2983
3028
|
relatedId,
|
|
2984
3029
|
ref
|
|
@@ -2993,9 +3038,10 @@ var RpcPeer = class {
|
|
|
2993
3038
|
} else if (call2 !== void 0) {
|
|
2994
3039
|
const send = () => {
|
|
2995
3040
|
if (call2.isCancelled) return;
|
|
2996
|
-
|
|
3041
|
+
const conn = this.wireConnection;
|
|
3042
|
+
if (conn !== void 0)
|
|
2997
3043
|
this.hub.systemCallSender.ok(
|
|
2998
|
-
|
|
3044
|
+
conn,
|
|
2999
3045
|
this.serializationFormat,
|
|
3000
3046
|
relatedId,
|
|
3001
3047
|
result
|
|
@@ -3009,9 +3055,10 @@ var RpcPeer = class {
|
|
|
3009
3055
|
if (call2 !== void 0) {
|
|
3010
3056
|
const send = () => {
|
|
3011
3057
|
if (call2.isCancelled) return;
|
|
3012
|
-
|
|
3058
|
+
const conn = this.wireConnection;
|
|
3059
|
+
if (conn !== void 0)
|
|
3013
3060
|
this.hub.systemCallSender.error(
|
|
3014
|
-
|
|
3061
|
+
conn,
|
|
3015
3062
|
this.serializationFormat,
|
|
3016
3063
|
relatedId,
|
|
3017
3064
|
e
|
|
@@ -3044,8 +3091,9 @@ var RpcPeer = class {
|
|
|
3044
3091
|
* Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
|
|
3045
3092
|
_armKeepAliveWatchdog() {
|
|
3046
3093
|
this._keepAliveTimer ??= setInterval(() => {
|
|
3047
|
-
|
|
3048
|
-
|
|
3094
|
+
const conn = this.wireConnection;
|
|
3095
|
+
if (conn !== void 0) {
|
|
3096
|
+
this.hub.systemCallSender.keepAlive(conn, this.serializationFormat, [
|
|
3049
3097
|
...this.remoteObjects.keys()
|
|
3050
3098
|
]);
|
|
3051
3099
|
}
|
|
@@ -3193,9 +3241,26 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3193
3241
|
connectWith(conn, isPeerChanged = true) {
|
|
3194
3242
|
const sentCalls = this.outboundCalls.getSentCalls();
|
|
3195
3243
|
this.setupConnection(conn);
|
|
3244
|
+
const whenHandshake = isPeerChanged ? void 0 : this._pendingHandshake = new PromiseSource5();
|
|
3245
|
+
this.hub.systemCallSender.handshake(
|
|
3246
|
+
conn,
|
|
3247
|
+
this.serializationFormat,
|
|
3248
|
+
this.id,
|
|
3249
|
+
this.hub.hubId,
|
|
3250
|
+
this.nextOwnHandshakeIndex()
|
|
3251
|
+
);
|
|
3196
3252
|
this._isConnected = true;
|
|
3197
3253
|
this.connectionStateChanged.trigger(3 /* Connected */);
|
|
3198
|
-
|
|
3254
|
+
if (whenHandshake === void 0) {
|
|
3255
|
+
void this._reconnect(sentCalls, true);
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
const whenClosed = new PromiseSource5();
|
|
3259
|
+
conn.closed.add(() => whenClosed.resolve());
|
|
3260
|
+
void Promise.race([whenHandshake, whenClosed]).then(() => {
|
|
3261
|
+
this._pendingHandshake = void 0;
|
|
3262
|
+
return this._reconnect(sentCalls, false);
|
|
3263
|
+
});
|
|
3199
3264
|
}
|
|
3200
3265
|
/** Kick off the reconnect loop. Idempotent — subsequent calls are
|
|
3201
3266
|
* no-ops; await `peer.whenRunning` if you need the loop's completion
|
|
@@ -3513,6 +3578,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3513
3578
|
return 2 /* Changed */;
|
|
3514
3579
|
}
|
|
3515
3580
|
_onHandshakeReceived(handshake) {
|
|
3581
|
+
this._remoteHandshakeIndex = handshake.Index ?? 0;
|
|
3516
3582
|
this._pendingHandshake?.resolve(handshake);
|
|
3517
3583
|
}
|
|
3518
3584
|
close() {
|