@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.js
CHANGED
|
@@ -879,7 +879,8 @@ var RpcCallTimeouts = class _RpcCallTimeouts {
|
|
|
879
879
|
static None = new _RpcCallTimeouts(Infinity, Infinity);
|
|
880
880
|
/** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
|
|
881
881
|
static Command = new _RpcCallTimeouts(1500, 1e4);
|
|
882
|
-
/** Max time a call may wait for
|
|
882
|
+
/** Max time a call may wait for a connection - before it is sent, and again for the
|
|
883
|
+
* duration of any outage it is still pending through. */
|
|
883
884
|
connectTimeoutMs;
|
|
884
885
|
/** Max time a sent call may stay unanswered before it is failed. */
|
|
885
886
|
runTimeoutMs;
|
|
@@ -1078,13 +1079,17 @@ var RpcSystemCallHandler = class {
|
|
|
1078
1079
|
case RpcSystemCalls.keepAlive: {
|
|
1079
1080
|
peer.notifyKeepAliveReceived();
|
|
1080
1081
|
const keepAliveIds = args[0];
|
|
1081
|
-
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.
|
|
1082
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.wireConnection !== void 0) {
|
|
1082
1083
|
const unknownIds = [];
|
|
1083
1084
|
for (const id of keepAliveIds)
|
|
1084
1085
|
if (peer.sharedObjects.get(id) === void 0)
|
|
1085
1086
|
unknownIds.push(id);
|
|
1086
1087
|
if (unknownIds.length > 0)
|
|
1087
|
-
peer.hub.systemCallSender.disconnect(
|
|
1088
|
+
peer.hub.systemCallSender.disconnect(
|
|
1089
|
+
peer.wireConnection,
|
|
1090
|
+
peer.serializationFormat,
|
|
1091
|
+
unknownIds
|
|
1092
|
+
);
|
|
1088
1093
|
}
|
|
1089
1094
|
break;
|
|
1090
1095
|
}
|
|
@@ -1130,16 +1135,24 @@ var RpcSystemCallHandler = class {
|
|
|
1130
1135
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1131
1136
|
if (sender)
|
|
1132
1137
|
sender.onAck(args[0], args[1]);
|
|
1133
|
-
else if (peer.
|
|
1134
|
-
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
|
+
);
|
|
1135
1144
|
break;
|
|
1136
1145
|
}
|
|
1137
1146
|
case RpcSystemCalls.ackEnd: {
|
|
1138
1147
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1139
1148
|
if (sender)
|
|
1140
1149
|
sender.onAckEnd(args[0]);
|
|
1141
|
-
else if (peer.
|
|
1142
|
-
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
|
+
);
|
|
1143
1156
|
break;
|
|
1144
1157
|
}
|
|
1145
1158
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1168,12 +1181,13 @@ var RpcSystemCallHandler = class {
|
|
|
1168
1181
|
* with the same `RelatedId`.
|
|
1169
1182
|
*/
|
|
1170
1183
|
_handleReconnect(relatedId, args, peer) {
|
|
1171
|
-
|
|
1184
|
+
const conn = peer.wireConnection;
|
|
1185
|
+
if (conn === void 0) return;
|
|
1172
1186
|
const handshakeIndex = args[0];
|
|
1173
1187
|
if (typeof handshakeIndex !== "number" || handshakeIndex !== peer.ownHandshakeIndex) {
|
|
1174
1188
|
const got = typeof handshakeIndex === "number" ? handshakeIndex : `<${typeof handshakeIndex}>`;
|
|
1175
1189
|
peer.hub.systemCallSender.error(
|
|
1176
|
-
|
|
1190
|
+
conn,
|
|
1177
1191
|
peer.serializationFormat,
|
|
1178
1192
|
relatedId,
|
|
1179
1193
|
new Error(
|
|
@@ -1184,7 +1198,7 @@ var RpcSystemCallHandler = class {
|
|
|
1184
1198
|
}
|
|
1185
1199
|
if (!peer.tryClaimReconnect()) {
|
|
1186
1200
|
peer.hub.systemCallSender.error(
|
|
1187
|
-
|
|
1201
|
+
conn,
|
|
1188
1202
|
peer.serializationFormat,
|
|
1189
1203
|
relatedId,
|
|
1190
1204
|
new Error("TooLateToReconnect: this connection was already reconnected")
|
|
@@ -1215,7 +1229,7 @@ var RpcSystemCallHandler = class {
|
|
|
1215
1229
|
}
|
|
1216
1230
|
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1217
1231
|
const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1218
|
-
peer.hub.systemCallSender.ok(
|
|
1232
|
+
peer.hub.systemCallSender.ok(conn, peer.serializationFormat, relatedId, responseValue);
|
|
1219
1233
|
}
|
|
1220
1234
|
};
|
|
1221
1235
|
function polymorphicPayloadError(method, got, expected) {
|
|
@@ -1398,6 +1412,8 @@ import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
|
1398
1412
|
// src/rpc-stream-sender.ts
|
|
1399
1413
|
import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
|
|
1400
1414
|
var { warnLog: warnLog5 } = getLogs("RpcSharedStream");
|
|
1415
|
+
function _ignore() {
|
|
1416
|
+
}
|
|
1401
1417
|
var DEFAULT_ACK_PERIOD = 256;
|
|
1402
1418
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1403
1419
|
var RTT_WINDOW_MS = 2e4;
|
|
@@ -1437,7 +1453,11 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1437
1453
|
// -- Pump state --
|
|
1438
1454
|
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1439
1455
|
_nextIndex = 0;
|
|
1440
|
-
|
|
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;
|
|
1441
1461
|
_started = new PromiseSource3();
|
|
1442
1462
|
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1443
1463
|
_lastAckedIndex = 0;
|
|
@@ -1544,7 +1564,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1544
1564
|
this._sendDisconnect();
|
|
1545
1565
|
return;
|
|
1546
1566
|
}
|
|
1547
|
-
} else if (this.
|
|
1567
|
+
} else if (this._isCompleted) {
|
|
1548
1568
|
this._sendDisconnect();
|
|
1549
1569
|
return;
|
|
1550
1570
|
} else if (mustReset && !this.allowReconnect) {
|
|
@@ -1565,7 +1585,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1565
1585
|
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1566
1586
|
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1567
1587
|
_sendDisconnect() {
|
|
1568
|
-
const conn = this.peer.
|
|
1588
|
+
const conn = this.peer.wireConnection;
|
|
1569
1589
|
if (conn)
|
|
1570
1590
|
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1571
1591
|
this.disconnect();
|
|
@@ -1579,13 +1599,13 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1579
1599
|
*
|
|
1580
1600
|
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1581
1601
|
* the item is considered "sent" from the pump's perspective so the replay
|
|
1582
|
-
* buffer indexing stays consistent. If
|
|
1602
|
+
* buffer indexing stays consistent. If the peer isn't connected, the wire
|
|
1583
1603
|
* message is dropped; the item will be resent from the replay buffer
|
|
1584
1604
|
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1585
1605
|
*/
|
|
1586
1606
|
sendItem(item) {
|
|
1587
|
-
if (this.
|
|
1588
|
-
const conn = this.peer.
|
|
1607
|
+
if (this._isDisposed) return;
|
|
1608
|
+
const conn = this.peer.wireConnection;
|
|
1589
1609
|
if (conn) {
|
|
1590
1610
|
this.peer.hub.systemCallSender.item(
|
|
1591
1611
|
conn,
|
|
@@ -1602,8 +1622,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1602
1622
|
}
|
|
1603
1623
|
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1604
1624
|
sendBatch(items) {
|
|
1605
|
-
if (this.
|
|
1606
|
-
const conn = this.peer.
|
|
1625
|
+
if (this._isDisposed || items.length === 0) return;
|
|
1626
|
+
const conn = this.peer.wireConnection;
|
|
1607
1627
|
if (conn) {
|
|
1608
1628
|
this.peer.hub.systemCallSender.batch(
|
|
1609
1629
|
conn,
|
|
@@ -1615,18 +1635,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1615
1635
|
}
|
|
1616
1636
|
this._nextIndex += items.length;
|
|
1617
1637
|
}
|
|
1618
|
-
/** 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). */
|
|
1619
1642
|
sendEnd(error) {
|
|
1620
|
-
if (this.
|
|
1621
|
-
|
|
1622
|
-
const conn = this.peer.
|
|
1643
|
+
if (this._isDisposed) return;
|
|
1644
|
+
const index = this._nextIndex++;
|
|
1645
|
+
const conn = this.peer.wireConnection;
|
|
1623
1646
|
if (!conn) return;
|
|
1624
1647
|
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1625
1648
|
this.peer.hub.systemCallSender.end(
|
|
1626
1649
|
conn,
|
|
1627
1650
|
this.peer.serializationFormat,
|
|
1628
1651
|
this.id.localId,
|
|
1629
|
-
|
|
1652
|
+
index,
|
|
1630
1653
|
errorInfo
|
|
1631
1654
|
);
|
|
1632
1655
|
}
|
|
@@ -1638,13 +1661,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1638
1661
|
*/
|
|
1639
1662
|
async writeFrom(source) {
|
|
1640
1663
|
await this._started;
|
|
1641
|
-
if (this.
|
|
1664
|
+
if (this._isDisposed) return;
|
|
1642
1665
|
const iterator = source[Symbol.asyncIterator]();
|
|
1643
1666
|
this._iterator = iterator;
|
|
1644
1667
|
const state = { iteratorDone: false };
|
|
1645
1668
|
try {
|
|
1646
1669
|
await this._run(iterator, state);
|
|
1647
1670
|
} finally {
|
|
1671
|
+
this._isCompleted = true;
|
|
1648
1672
|
if (state.iteratorDone && this._iterator === iterator) {
|
|
1649
1673
|
this._iterator = null;
|
|
1650
1674
|
}
|
|
@@ -1657,7 +1681,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1657
1681
|
* With `bufferSize > ackAdvance` the local ring buffer holds more than the
|
|
1658
1682
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1659
1683
|
*/
|
|
1660
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition --
|
|
1684
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _isDisposed changes across awaits */
|
|
1661
1685
|
async _run(iterator, state) {
|
|
1662
1686
|
const buffer = new RingBuffer(this.bufferSize + 1);
|
|
1663
1687
|
const isRealTime = this.isRealTime;
|
|
@@ -1690,7 +1714,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1690
1714
|
if (r === pending)
|
|
1691
1715
|
return false;
|
|
1692
1716
|
whenMovedNext = null;
|
|
1693
|
-
if (this.
|
|
1717
|
+
if (this._isDisposed) return false;
|
|
1694
1718
|
if (r.done) {
|
|
1695
1719
|
buffer.pushTail(_endItem);
|
|
1696
1720
|
isFullyBuffered = true;
|
|
@@ -1714,105 +1738,107 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1714
1738
|
return true;
|
|
1715
1739
|
}
|
|
1716
1740
|
};
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
await this._waitAckReady();
|
|
1721
|
-
if (this._ended) return;
|
|
1722
|
-
ack = this._tryProcessAcks();
|
|
1741
|
+
nextAck:
|
|
1742
|
+
while (true) {
|
|
1743
|
+
let ack = this._tryProcessAcks();
|
|
1723
1744
|
if (!ack) {
|
|
1724
|
-
|
|
1725
|
-
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
|
+
}
|
|
1726
1752
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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) {
|
|
1742
1775
|
state.iteratorDone = true;
|
|
1743
|
-
|
|
1744
|
-
item = {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1776
|
+
isFullyBuffered = true;
|
|
1777
|
+
item = {
|
|
1778
|
+
kind: "error",
|
|
1779
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1780
|
+
};
|
|
1748
1781
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
isFullyBuffered
|
|
1752
|
-
item = {
|
|
1753
|
-
kind: "error",
|
|
1754
|
-
error: e instanceof Error ? e : new Error(String(e))
|
|
1755
|
-
};
|
|
1782
|
+
buffer.pushTail(item);
|
|
1783
|
+
fireBuffered();
|
|
1784
|
+
if (accepted || isFullyBuffered) break;
|
|
1756
1785
|
}
|
|
1757
|
-
buffer.pushTail(item);
|
|
1758
|
-
fireBuffered();
|
|
1759
|
-
if (accepted || isFullyBuffered) break;
|
|
1760
1786
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
bufferStart += shift;
|
|
1766
|
-
}
|
|
1767
|
-
if (this._nextIndex < bufferStart) {
|
|
1768
|
-
if (!this._ended)
|
|
1769
|
-
this.sendEnd(new Error("Stream position unavailable."));
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
let bufferIndex = this._nextIndex - bufferStart;
|
|
1773
|
-
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1774
|
-
while (this._nextIndex < maxIndex) {
|
|
1775
|
-
if (this._ended) return;
|
|
1776
|
-
if (isRealTime) {
|
|
1777
|
-
while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {
|
|
1778
|
-
if (!await bufferNext(true, true))
|
|
1779
|
-
break;
|
|
1780
|
-
}
|
|
1781
|
-
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && !this._hasPendingAck) {
|
|
1782
|
-
await bufferNext(false, true);
|
|
1783
|
-
}
|
|
1784
|
-
if (ack.nextIndex > 0) {
|
|
1785
|
-
const result = _compactBufferedUnsentSuffix(
|
|
1786
|
-
buffer,
|
|
1787
|
-
bufferIndex,
|
|
1788
|
-
this.canSkipTo
|
|
1789
|
-
);
|
|
1790
|
-
bufferIndex = result.newIndex;
|
|
1791
|
-
if (result.skipped > 0)
|
|
1792
|
-
this._skipCount += result.skipped;
|
|
1793
|
-
}
|
|
1794
|
-
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;
|
|
1795
1791
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
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;
|
|
1806
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
|
+
}
|
|
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);
|
|
1807
1840
|
}
|
|
1808
|
-
if (this._nextIndex >= maxIndex)
|
|
1809
|
-
break;
|
|
1810
|
-
if (isFullyBuffered) break;
|
|
1811
|
-
if (!buffer.hasRemainingCapacity) break;
|
|
1812
|
-
if (!isRealTime)
|
|
1813
|
-
await bufferNext(false, false);
|
|
1814
1841
|
}
|
|
1815
|
-
}
|
|
1816
1842
|
}
|
|
1817
1843
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1818
1844
|
get _hasPendingAck() {
|
|
@@ -1837,13 +1863,14 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1837
1863
|
}
|
|
1838
1864
|
return { nextIndex, mustReset };
|
|
1839
1865
|
}
|
|
1840
|
-
/** Wait until an ACK is pending (or
|
|
1866
|
+
/** Wait until an ACK is pending (or the sender is torn down). */
|
|
1841
1867
|
async _waitAckReady() {
|
|
1842
|
-
if (this._hasPendingAck || this.
|
|
1868
|
+
if (this._hasPendingAck || this._isDisposed)
|
|
1843
1869
|
return;
|
|
1844
|
-
this._whenAckReady
|
|
1845
|
-
await
|
|
1846
|
-
this._whenAckReady
|
|
1870
|
+
const whenAckReady = this._whenAckReady ??= new PromiseSource3();
|
|
1871
|
+
await whenAckReady;
|
|
1872
|
+
if (this._whenAckReady === whenAckReady)
|
|
1873
|
+
this._whenAckReady = null;
|
|
1847
1874
|
}
|
|
1848
1875
|
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1849
1876
|
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
@@ -1869,8 +1896,8 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1869
1896
|
reconnect() {
|
|
1870
1897
|
}
|
|
1871
1898
|
disconnect() {
|
|
1872
|
-
if (this.
|
|
1873
|
-
this.
|
|
1899
|
+
if (this._isDisposed) return;
|
|
1900
|
+
this._isDisposed = true;
|
|
1874
1901
|
this._abortController.abort();
|
|
1875
1902
|
if (!this._started.isCompleted) {
|
|
1876
1903
|
this._started.resolve();
|
|
@@ -2029,8 +2056,11 @@ var RpcStream = class _RpcStream {
|
|
|
2029
2056
|
}
|
|
2030
2057
|
}
|
|
2031
2058
|
/**
|
|
2032
|
-
* For local streams: resolves
|
|
2033
|
-
*
|
|
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.
|
|
2034
2064
|
*/
|
|
2035
2065
|
get whenSent() {
|
|
2036
2066
|
if (!this._whenSent)
|
|
@@ -2296,7 +2326,7 @@ var RpcStream = class _RpcStream {
|
|
|
2296
2326
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
2297
2327
|
_sendAck(nextIndex, mustReset) {
|
|
2298
2328
|
this._ackSentUpTo = nextIndex;
|
|
2299
|
-
const conn = this.peer.
|
|
2329
|
+
const conn = this.peer.wireConnection;
|
|
2300
2330
|
if (conn) {
|
|
2301
2331
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
2302
2332
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -2309,7 +2339,7 @@ var RpcStream = class _RpcStream {
|
|
|
2309
2339
|
}
|
|
2310
2340
|
}
|
|
2311
2341
|
_sendAckEnd() {
|
|
2312
|
-
const conn = this.peer.
|
|
2342
|
+
const conn = this.peer.wireConnection;
|
|
2313
2343
|
if (conn) {
|
|
2314
2344
|
this.peer.hub.systemCallSender.ackEnd(
|
|
2315
2345
|
conn,
|
|
@@ -2673,6 +2703,8 @@ var RpcPeer = class {
|
|
|
2673
2703
|
/** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
|
|
2674
2704
|
* for the current connection. Useful for diagnostics. */
|
|
2675
2705
|
_lastKeepAliveAt = 0;
|
|
2706
|
+
/** When the current outage began; 0 until the peer has been connected at least once. */
|
|
2707
|
+
_disconnectedAt = 0;
|
|
2676
2708
|
/** Outbound keep-alive send period. Initialized from `hub.limits` at
|
|
2677
2709
|
* construction; set directly to override on a single peer. */
|
|
2678
2710
|
keepAlivePeriodMs;
|
|
@@ -2693,9 +2725,10 @@ var RpcPeer = class {
|
|
|
2693
2725
|
}
|
|
2694
2726
|
/** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
|
|
2695
2727
|
* the connect timeout while a call still awaits the wire, the run timeout
|
|
2696
|
-
* once it has been sent
|
|
2697
|
-
*
|
|
2698
|
-
*
|
|
2728
|
+
* once it has been sent, and the connect timeout again for any pending call while the
|
|
2729
|
+
* peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,
|
|
2730
|
+
* per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts
|
|
2731
|
+
* (queries) never time out here. */
|
|
2699
2732
|
_maintainOutboundCalls() {
|
|
2700
2733
|
const now = Date.now();
|
|
2701
2734
|
const canRunTimeout = this._isConnected && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);
|
|
@@ -2704,7 +2737,11 @@ var RpcPeer = class {
|
|
|
2704
2737
|
if (timeouts === void 0 || call.result.isCompleted)
|
|
2705
2738
|
continue;
|
|
2706
2739
|
let timedOut;
|
|
2707
|
-
if (
|
|
2740
|
+
if (!this._isConnected) {
|
|
2741
|
+
const since = Math.max(this._disconnectedAt, call.startedAt);
|
|
2742
|
+
if (now - since >= timeouts.connectTimeoutMs)
|
|
2743
|
+
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2744
|
+
} else if (call.sentAt === 0) {
|
|
2708
2745
|
if (now - call.startedAt >= timeouts.connectTimeoutMs)
|
|
2709
2746
|
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2710
2747
|
} else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)
|
|
@@ -2719,6 +2756,18 @@ var RpcPeer = class {
|
|
|
2719
2756
|
get connection() {
|
|
2720
2757
|
return this._connection;
|
|
2721
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
|
+
}
|
|
2722
2771
|
get connectionState() {
|
|
2723
2772
|
return this._connectionState;
|
|
2724
2773
|
}
|
|
@@ -2747,10 +2796,13 @@ var RpcPeer = class {
|
|
|
2747
2796
|
this._connectionState = value;
|
|
2748
2797
|
if (value === 3 /* Connected */) {
|
|
2749
2798
|
this._isConnected = true;
|
|
2799
|
+
this._disconnectedAt = 0;
|
|
2750
2800
|
this._armKeepAliveWatchdog();
|
|
2751
|
-
} else
|
|
2752
|
-
if (previousState === 3 /* Connected */)
|
|
2801
|
+
} else {
|
|
2802
|
+
if (previousState === 3 /* Connected */) {
|
|
2753
2803
|
this._isConnected = false;
|
|
2804
|
+
this._disconnectedAt = Date.now();
|
|
2805
|
+
}
|
|
2754
2806
|
this._disarmKeepAliveWatchdog();
|
|
2755
2807
|
}
|
|
2756
2808
|
this.connectionStateChanged.trigger(value);
|
|
@@ -2775,6 +2827,8 @@ var RpcPeer = class {
|
|
|
2775
2827
|
conn.closed.add((ev) => {
|
|
2776
2828
|
if (this._connection !== conn) return;
|
|
2777
2829
|
this._connection = void 0;
|
|
2830
|
+
if (this._isConnected)
|
|
2831
|
+
this._disconnectedAt = Date.now();
|
|
2778
2832
|
this._isConnected = false;
|
|
2779
2833
|
if (ev.reason)
|
|
2780
2834
|
infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);
|
|
@@ -2834,8 +2888,9 @@ var RpcPeer = class {
|
|
|
2834
2888
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2835
2889
|
outboundCall.result.reject(cancellationError("Call cancelled."));
|
|
2836
2890
|
outboundCall.onDisconnect();
|
|
2837
|
-
|
|
2838
|
-
|
|
2891
|
+
const conn = this.wireConnection;
|
|
2892
|
+
if (conn !== void 0)
|
|
2893
|
+
this.hub.systemCallSender.cancel(conn, this.serializationFormat, callId);
|
|
2839
2894
|
}
|
|
2840
2895
|
};
|
|
2841
2896
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2878,9 +2933,11 @@ var RpcPeer = class {
|
|
|
2878
2933
|
this._setConnectionState(0 /* Disconnected */);
|
|
2879
2934
|
infoLog?.log(`'${this.ref}': Stopped`);
|
|
2880
2935
|
}
|
|
2881
|
-
/** 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. */
|
|
2882
2939
|
_sendWireData(data) {
|
|
2883
|
-
if (this._connection === void 0) return;
|
|
2940
|
+
if (!this._isConnected || this._connection === void 0) return;
|
|
2884
2941
|
if (typeof data === "string") {
|
|
2885
2942
|
this._connection.send(data);
|
|
2886
2943
|
} else {
|
|
@@ -2963,9 +3020,10 @@ var RpcPeer = class {
|
|
|
2963
3020
|
const ref = stream.toRef(this);
|
|
2964
3021
|
const send = () => {
|
|
2965
3022
|
if (call2?.isCancelled === true) return;
|
|
2966
|
-
|
|
3023
|
+
const conn = this.wireConnection;
|
|
3024
|
+
if (conn !== void 0)
|
|
2967
3025
|
this.hub.systemCallSender.ok(
|
|
2968
|
-
|
|
3026
|
+
conn,
|
|
2969
3027
|
this.serializationFormat,
|
|
2970
3028
|
relatedId,
|
|
2971
3029
|
ref
|
|
@@ -2980,9 +3038,10 @@ var RpcPeer = class {
|
|
|
2980
3038
|
} else if (call2 !== void 0) {
|
|
2981
3039
|
const send = () => {
|
|
2982
3040
|
if (call2.isCancelled) return;
|
|
2983
|
-
|
|
3041
|
+
const conn = this.wireConnection;
|
|
3042
|
+
if (conn !== void 0)
|
|
2984
3043
|
this.hub.systemCallSender.ok(
|
|
2985
|
-
|
|
3044
|
+
conn,
|
|
2986
3045
|
this.serializationFormat,
|
|
2987
3046
|
relatedId,
|
|
2988
3047
|
result
|
|
@@ -2996,9 +3055,10 @@ var RpcPeer = class {
|
|
|
2996
3055
|
if (call2 !== void 0) {
|
|
2997
3056
|
const send = () => {
|
|
2998
3057
|
if (call2.isCancelled) return;
|
|
2999
|
-
|
|
3058
|
+
const conn = this.wireConnection;
|
|
3059
|
+
if (conn !== void 0)
|
|
3000
3060
|
this.hub.systemCallSender.error(
|
|
3001
|
-
|
|
3061
|
+
conn,
|
|
3002
3062
|
this.serializationFormat,
|
|
3003
3063
|
relatedId,
|
|
3004
3064
|
e
|
|
@@ -3031,8 +3091,9 @@ var RpcPeer = class {
|
|
|
3031
3091
|
* Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
|
|
3032
3092
|
_armKeepAliveWatchdog() {
|
|
3033
3093
|
this._keepAliveTimer ??= setInterval(() => {
|
|
3034
|
-
|
|
3035
|
-
|
|
3094
|
+
const conn = this.wireConnection;
|
|
3095
|
+
if (conn !== void 0) {
|
|
3096
|
+
this.hub.systemCallSender.keepAlive(conn, this.serializationFormat, [
|
|
3036
3097
|
...this.remoteObjects.keys()
|
|
3037
3098
|
]);
|
|
3038
3099
|
}
|
|
@@ -3180,9 +3241,26 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3180
3241
|
connectWith(conn, isPeerChanged = true) {
|
|
3181
3242
|
const sentCalls = this.outboundCalls.getSentCalls();
|
|
3182
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
|
+
);
|
|
3183
3252
|
this._isConnected = true;
|
|
3184
3253
|
this.connectionStateChanged.trigger(3 /* Connected */);
|
|
3185
|
-
|
|
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
|
+
});
|
|
3186
3264
|
}
|
|
3187
3265
|
/** Kick off the reconnect loop. Idempotent — subsequent calls are
|
|
3188
3266
|
* no-ops; await `peer.whenRunning` if you need the loop's completion
|
|
@@ -3500,6 +3578,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3500
3578
|
return 2 /* Changed */;
|
|
3501
3579
|
}
|
|
3502
3580
|
_onHandshakeReceived(handshake) {
|
|
3581
|
+
this._remoteHandshakeIndex = handshake.Index ?? 0;
|
|
3503
3582
|
this._pendingHandshake?.resolve(handshake);
|
|
3504
3583
|
}
|
|
3505
3584
|
close() {
|