@actuallab/rpc 13.0.25 → 13.0.167
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 +658 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +171 -11
- package/dist/index.d.ts +171 -11
- package/dist/index.js +653 -176
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -34,8 +34,10 @@ __export(index_exports, {
|
|
|
34
34
|
ENVELOPE_DELIMITER: () => ENVELOPE_DELIMITER,
|
|
35
35
|
FRAME_DELIMITER: () => FRAME_DELIMITER,
|
|
36
36
|
IncreasingSeqCompressor: () => IncreasingSeqCompressor,
|
|
37
|
+
REMOTE_EXCEPTION_TYPE_REF: () => REMOTE_EXCEPTION_TYPE_REF,
|
|
37
38
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT: () => RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
38
39
|
RpcCallStage: () => RpcCallStage,
|
|
40
|
+
RpcCallTimeouts: () => RpcCallTimeouts,
|
|
39
41
|
RpcCallTypeId: () => RpcCallTypeId,
|
|
40
42
|
RpcClientPeer: () => RpcClientPeer,
|
|
41
43
|
RpcClientPeerReconnectDelayer: () => RpcClientPeerReconnectDelayer,
|
|
@@ -90,6 +92,7 @@ __export(index_exports, {
|
|
|
90
92
|
isConnected: () => isConnected,
|
|
91
93
|
likelyConnected: () => likelyConnected,
|
|
92
94
|
parseStreamRef: () => parseStreamRef,
|
|
95
|
+
readPolymorphismMarker: () => readPolymorphismMarker,
|
|
93
96
|
resolveStreamRefs: () => resolveStreamRefs,
|
|
94
97
|
rpcMethod: () => rpcMethod,
|
|
95
98
|
rpcService: () => rpcService,
|
|
@@ -101,6 +104,7 @@ __export(index_exports, {
|
|
|
101
104
|
splitBinaryFrame: () => splitBinaryFrame,
|
|
102
105
|
splitCompactBinaryFrame: () => splitCompactBinaryFrame,
|
|
103
106
|
splitFrame: () => splitFrame,
|
|
107
|
+
toExceptionInfo: () => toExceptionInfo,
|
|
104
108
|
wireMethodName: () => wireMethodName,
|
|
105
109
|
xxh3_64: () => xxh3_64,
|
|
106
110
|
xxh3_64str: () => xxh3_64str
|
|
@@ -271,6 +275,24 @@ function readVarUint(data, offset) {
|
|
|
271
275
|
} while (shift < 35);
|
|
272
276
|
return { value: value >>> 0, bytesRead };
|
|
273
277
|
}
|
|
278
|
+
function skipHeaders(data, pos, headerCount) {
|
|
279
|
+
for (let h = 0; h < headerCount; h++) {
|
|
280
|
+
const keyLen = data[pos++];
|
|
281
|
+
pos += keyLen;
|
|
282
|
+
const valLen = readVarUint(data, pos);
|
|
283
|
+
pos += valLen.bytesRead + valLen.value;
|
|
284
|
+
}
|
|
285
|
+
return pos;
|
|
286
|
+
}
|
|
287
|
+
function readPolymorphismMarker(data, offset) {
|
|
288
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
289
|
+
const length = view.getUint16(offset, true);
|
|
290
|
+
if (length === 0)
|
|
291
|
+
return { bytesRead: 2, isNull: false };
|
|
292
|
+
if (length === 1)
|
|
293
|
+
return { bytesRead: 2, isNull: true };
|
|
294
|
+
throw new Error("polymorphic payloads are not supported by the TS client");
|
|
295
|
+
}
|
|
274
296
|
function concatUint8Arrays(arrays) {
|
|
275
297
|
let totalLength = 0;
|
|
276
298
|
for (const arr of arrays) totalLength += arr.length;
|
|
@@ -334,14 +356,6 @@ function deserializeBinaryMessage(data, offset, decoder = defaultBinaryDecoder)
|
|
|
334
356
|
pos += methodLen.value;
|
|
335
357
|
const argDataLen = view.getInt32(pos, true);
|
|
336
358
|
pos += 4;
|
|
337
|
-
if (headerCount > 0) {
|
|
338
|
-
for (let h = 0; h < headerCount; h++) {
|
|
339
|
-
const keyLen = data[pos++];
|
|
340
|
-
pos += keyLen;
|
|
341
|
-
const valLen = readVarUint(data, pos);
|
|
342
|
-
pos += valLen.bytesRead + valLen.value;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
359
|
const args = [];
|
|
346
360
|
const argEnd = pos + argDataLen;
|
|
347
361
|
if (argDataLen > 0) {
|
|
@@ -349,8 +363,10 @@ function deserializeBinaryMessage(data, offset, decoder = defaultBinaryDecoder)
|
|
|
349
363
|
for (const decoded of decoder.decodeMulti(argSlice)) {
|
|
350
364
|
args.push(decoded);
|
|
351
365
|
}
|
|
352
|
-
pos = argEnd;
|
|
353
366
|
}
|
|
367
|
+
pos = argEnd;
|
|
368
|
+
if (headerCount > 0)
|
|
369
|
+
pos = skipHeaders(data, pos, headerCount);
|
|
354
370
|
const message = {
|
|
355
371
|
Method: method,
|
|
356
372
|
RelatedId: relId.value,
|
|
@@ -428,14 +444,6 @@ function deserializeCompactBinaryMessage(data, offset, registry, decoder = defau
|
|
|
428
444
|
const method = registry?.getName(methodHash | 0) ?? `<hash:0x${methodHash.toString(16).padStart(8, "0")}>`;
|
|
429
445
|
const argDataLen = view.getInt32(pos, true);
|
|
430
446
|
pos += 4;
|
|
431
|
-
if (headerCount > 0) {
|
|
432
|
-
for (let h = 0; h < headerCount; h++) {
|
|
433
|
-
const keyLen = data[pos++];
|
|
434
|
-
pos += keyLen;
|
|
435
|
-
const valLen = readVarUint(data, pos);
|
|
436
|
-
pos += valLen.bytesRead + valLen.value;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
447
|
const args = [];
|
|
440
448
|
const argEnd = pos + argDataLen;
|
|
441
449
|
if (argDataLen > 0) {
|
|
@@ -443,8 +451,10 @@ function deserializeCompactBinaryMessage(data, offset, registry, decoder = defau
|
|
|
443
451
|
for (const decoded of decoder.decodeMulti(argSlice)) {
|
|
444
452
|
args.push(decoded);
|
|
445
453
|
}
|
|
446
|
-
pos = argEnd;
|
|
447
454
|
}
|
|
455
|
+
pos = argEnd;
|
|
456
|
+
if (headerCount > 0)
|
|
457
|
+
pos = skipHeaders(data, pos, headerCount);
|
|
448
458
|
const message = {
|
|
449
459
|
Method: method,
|
|
450
460
|
RelatedId: relId.value,
|
|
@@ -714,7 +724,8 @@ function defineRpcService(name, methods) {
|
|
|
714
724
|
callTypeId: input.callTypeId ?? 0,
|
|
715
725
|
stream: input.returns === RpcType.stream,
|
|
716
726
|
noWait: isNoWait,
|
|
717
|
-
remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
|
|
727
|
+
remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default,
|
|
728
|
+
timeouts: input.timeouts
|
|
718
729
|
});
|
|
719
730
|
}
|
|
720
731
|
return { name, methods: map };
|
|
@@ -748,6 +759,14 @@ var RpcOutboundCall = class {
|
|
|
748
759
|
removeOnOk = true;
|
|
749
760
|
/** The serialized wire data — stored for re-sending on reconnect. */
|
|
750
761
|
serializedWireData = "";
|
|
762
|
+
/** Per-call connect/run timeouts enforced by the peer's maintenance loop
|
|
763
|
+
* (R12). `undefined` means unbounded (the query default). */
|
|
764
|
+
timeouts;
|
|
765
|
+
/** `Date.now()` when this call was created (used for the connect timeout). */
|
|
766
|
+
startedAt = Date.now();
|
|
767
|
+
/** `Date.now()` when this call was last put on the wire, or 0 if it hasn't
|
|
768
|
+
* been sent yet (used for the run timeout). */
|
|
769
|
+
sentAt = 0;
|
|
751
770
|
/**
|
|
752
771
|
* Bitfield of {@link RpcCallStage} flags indicating how far this call
|
|
753
772
|
* has progressed. Used by the `$sys.Reconnect` protocol to tell the
|
|
@@ -837,20 +856,77 @@ var RpcInboundCall = class {
|
|
|
837
856
|
callId;
|
|
838
857
|
method;
|
|
839
858
|
args;
|
|
859
|
+
/** 0 = still processing, 1 = result computed and sent at least once. */
|
|
860
|
+
completedStage = 0;
|
|
861
|
+
_resend;
|
|
862
|
+
_abortController = new AbortController();
|
|
840
863
|
constructor(callId, method, args) {
|
|
841
864
|
this.callId = callId;
|
|
842
865
|
this.method = method;
|
|
843
866
|
this.args = args;
|
|
844
867
|
}
|
|
868
|
+
/** Aborted when the remote peer cancels this call via `$sys.Cancel` (R17).
|
|
869
|
+
* Threaded into the service handler so it can abort in-flight work. */
|
|
870
|
+
get signal() {
|
|
871
|
+
return this._abortController.signal;
|
|
872
|
+
}
|
|
873
|
+
/** True once the remote peer cancelled this call — the response is then
|
|
874
|
+
* suppressed even though the call may still be registered (R9 dedup). */
|
|
875
|
+
get isCancelled() {
|
|
876
|
+
return this._abortController.signal.aborted;
|
|
877
|
+
}
|
|
878
|
+
cancel() {
|
|
879
|
+
this._abortController.abort();
|
|
880
|
+
}
|
|
881
|
+
// Records the closure that re-sends this call's already-computed result;
|
|
882
|
+
// marks the call as completed. Mirrors .NET's post-`ProcessStage1Plus` state.
|
|
883
|
+
setResult(resend) {
|
|
884
|
+
this._resend = resend;
|
|
885
|
+
this.completedStage = 1;
|
|
886
|
+
}
|
|
887
|
+
// Re-sends the result to the remote peer if it was already computed.
|
|
888
|
+
// A resent frame for an in-flight call is a no-op — the original dispatch
|
|
889
|
+
// will send the result when it completes. Mirrors .NET
|
|
890
|
+
// `RpcInboundCall.TryReprocess`.
|
|
891
|
+
resendResult() {
|
|
892
|
+
if (this.completedStage >= 1)
|
|
893
|
+
this._resend?.();
|
|
894
|
+
}
|
|
845
895
|
};
|
|
846
896
|
var RpcInboundCallTracker = class {
|
|
847
897
|
_calls = /* @__PURE__ */ new Map();
|
|
898
|
+
_completedIds = [];
|
|
899
|
+
/** Cap on retained completed calls — see `RpcLimits.completedInboundCallsLimit`. */
|
|
900
|
+
completedCallsLimit = 1e3;
|
|
848
901
|
get size() {
|
|
849
902
|
return this._calls.size;
|
|
850
903
|
}
|
|
851
904
|
register(call) {
|
|
852
905
|
this._calls.set(call.callId, call);
|
|
853
906
|
}
|
|
907
|
+
// Registers `call` unless its id is already tracked, in which case the
|
|
908
|
+
// existing call is returned. Mirrors .NET `RpcInboundCallTracker.GetOrRegister`.
|
|
909
|
+
getOrRegister(call) {
|
|
910
|
+
const existing = this._calls.get(call.callId);
|
|
911
|
+
if (existing !== void 0)
|
|
912
|
+
return existing;
|
|
913
|
+
this._calls.set(call.callId, call);
|
|
914
|
+
return call;
|
|
915
|
+
}
|
|
916
|
+
// Keeps the completed `call` registered for duplicate-frame dedup, evicting
|
|
917
|
+
// the oldest completed calls beyond `completedCallsLimit`. Deviation from
|
|
918
|
+
// .NET (which unregisters a call once its result is sent): TS peers
|
|
919
|
+
// blind-resend after reconnect, so a completed call must stay resolvable
|
|
920
|
+
// to avoid re-executing the handler — but only within a bounded window.
|
|
921
|
+
markCompleted(call) {
|
|
922
|
+
if (this._calls.get(call.callId) !== call)
|
|
923
|
+
return;
|
|
924
|
+
this._completedIds.push(call.callId);
|
|
925
|
+
while (this._completedIds.length > this.completedCallsLimit) {
|
|
926
|
+
const id = this._completedIds.shift();
|
|
927
|
+
this._calls.delete(id);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
854
930
|
get(callId) {
|
|
855
931
|
return this._calls.get(callId);
|
|
856
932
|
}
|
|
@@ -861,6 +937,23 @@ var RpcInboundCallTracker = class {
|
|
|
861
937
|
}
|
|
862
938
|
clear() {
|
|
863
939
|
this._calls.clear();
|
|
940
|
+
this._completedIds.length = 0;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// src/rpc-call-timeouts.ts
|
|
945
|
+
var RpcCallTimeouts = class _RpcCallTimeouts {
|
|
946
|
+
/** Both timeouts unbounded — the query default (.NET `RpcCallTimeouts.None`). */
|
|
947
|
+
static None = new _RpcCallTimeouts(Infinity, Infinity);
|
|
948
|
+
/** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
|
|
949
|
+
static Command = new _RpcCallTimeouts(1500, 1e4);
|
|
950
|
+
/** Max time a call may wait for the connection before it is sent. */
|
|
951
|
+
connectTimeoutMs;
|
|
952
|
+
/** Max time a sent call may stay unanswered before it is failed. */
|
|
953
|
+
runTimeoutMs;
|
|
954
|
+
constructor(connectTimeoutMs = Infinity, runTimeoutMs = Infinity) {
|
|
955
|
+
this.connectTimeoutMs = connectTimeoutMs;
|
|
956
|
+
this.runTimeoutMs = runTimeoutMs;
|
|
864
957
|
}
|
|
865
958
|
};
|
|
866
959
|
|
|
@@ -874,6 +967,12 @@ var RpcError = class extends Error {
|
|
|
874
967
|
this.typeName = typeName;
|
|
875
968
|
}
|
|
876
969
|
};
|
|
970
|
+
var REMOTE_EXCEPTION_TYPE_REF = "ActualLab.Serialization.RemoteException, ActualLab.Core";
|
|
971
|
+
function toExceptionInfo(error) {
|
|
972
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
973
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
974
|
+
return { TypeRef: REMOTE_EXCEPTION_TYPE_REF, Message: `${name}: ${message}` };
|
|
975
|
+
}
|
|
877
976
|
|
|
878
977
|
// src/increasing-seq-compressor.ts
|
|
879
978
|
var IncreasingSeqCompressor = {
|
|
@@ -978,6 +1077,7 @@ var import_core5 = require("@actuallab/core");
|
|
|
978
1077
|
var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
|
|
979
1078
|
var DEFAULT_ACK_PERIOD = 256;
|
|
980
1079
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
1080
|
+
var RTT_WINDOW_MS = 2e4;
|
|
981
1081
|
var RpcStreamSender = class _RpcStreamSender {
|
|
982
1082
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
983
1083
|
id;
|
|
@@ -1019,6 +1119,9 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1019
1119
|
_lastAckedIndex = 0;
|
|
1020
1120
|
_abortController = new AbortController();
|
|
1021
1121
|
_iterator = null;
|
|
1122
|
+
// -- RTT sampling (send → matching ack round trips, windowed minimum) --
|
|
1123
|
+
_pendingSendTimes = [];
|
|
1124
|
+
_rttSamples = [];
|
|
1022
1125
|
// -- Recorder-controller metrics (Step 9.1) --
|
|
1023
1126
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1024
1127
|
_skipCount = 0;
|
|
@@ -1043,6 +1146,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1043
1146
|
get skipCount() {
|
|
1044
1147
|
return this._skipCount;
|
|
1045
1148
|
}
|
|
1149
|
+
// Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
|
|
1150
|
+
// RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
|
|
1151
|
+
get minRttMs() {
|
|
1152
|
+
const cutoffMs = Date.now() - RTT_WINDOW_MS;
|
|
1153
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1154
|
+
this._rttSamples.shift();
|
|
1155
|
+
if (this._rttSamples.length === 0)
|
|
1156
|
+
return -1;
|
|
1157
|
+
let min = this._rttSamples[0].rttMs;
|
|
1158
|
+
for (const s of this._rttSamples) {
|
|
1159
|
+
if (s.rttMs < min)
|
|
1160
|
+
min = s.rttMs;
|
|
1161
|
+
}
|
|
1162
|
+
return min;
|
|
1163
|
+
}
|
|
1046
1164
|
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
1047
1165
|
_acks = [];
|
|
1048
1166
|
_whenAckReady = null;
|
|
@@ -1078,13 +1196,39 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1078
1196
|
*/
|
|
1079
1197
|
onAck(nextIndex, hostId) {
|
|
1080
1198
|
const mustReset = hostId !== "" && hostId !== _RpcStreamSender._emptyGuid;
|
|
1081
|
-
if (
|
|
1199
|
+
if (mustReset && hostId !== this.id.hostId) {
|
|
1200
|
+
this._sendDisconnect();
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (!this._started.isCompleted) {
|
|
1204
|
+
if (mustReset && nextIndex === 0)
|
|
1205
|
+
this._started.resolve();
|
|
1206
|
+
else {
|
|
1207
|
+
this._sendDisconnect();
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
} else if (this._ended) {
|
|
1211
|
+
this._sendDisconnect();
|
|
1212
|
+
return;
|
|
1213
|
+
} else if (mustReset && !this.allowReconnect) {
|
|
1214
|
+
this._sendDisconnect();
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
this._sampleRtt(nextIndex, mustReset);
|
|
1082
1218
|
this._acks.push({ nextIndex, mustReset });
|
|
1083
1219
|
if (this._whenAckReady) {
|
|
1084
1220
|
this._whenAckReady.resolve();
|
|
1085
1221
|
this._whenAckReady = null;
|
|
1086
1222
|
}
|
|
1087
1223
|
}
|
|
1224
|
+
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1225
|
+
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1226
|
+
_sendDisconnect() {
|
|
1227
|
+
const conn = this.peer.connection;
|
|
1228
|
+
if (conn)
|
|
1229
|
+
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1230
|
+
this.disconnect();
|
|
1231
|
+
}
|
|
1088
1232
|
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1089
1233
|
onAckEnd(_hostId) {
|
|
1090
1234
|
this.disconnect();
|
|
@@ -1109,6 +1253,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1109
1253
|
this._nextIndex,
|
|
1110
1254
|
item
|
|
1111
1255
|
);
|
|
1256
|
+
this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
|
|
1112
1257
|
}
|
|
1113
1258
|
this._nextIndex++;
|
|
1114
1259
|
}
|
|
@@ -1133,7 +1278,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1133
1278
|
this._ended = true;
|
|
1134
1279
|
const conn = this.peer.connection;
|
|
1135
1280
|
if (!conn) return;
|
|
1136
|
-
const errorInfo = error ?
|
|
1281
|
+
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1137
1282
|
this.peer.hub.systemCallSender.end(
|
|
1138
1283
|
conn,
|
|
1139
1284
|
this.peer.serializationFormat,
|
|
@@ -1351,6 +1496,26 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1351
1496
|
await this._whenAckReady;
|
|
1352
1497
|
this._whenAckReady = null;
|
|
1353
1498
|
}
|
|
1499
|
+
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1500
|
+
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
1501
|
+
_sampleRtt(ackNextIndex, mustReset) {
|
|
1502
|
+
if (mustReset) {
|
|
1503
|
+
this._pendingSendTimes.length = 0;
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
let sentAtMs = -1;
|
|
1507
|
+
while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
|
|
1508
|
+
sentAtMs = this._pendingSendTimes[0].sentAtMs;
|
|
1509
|
+
this._pendingSendTimes.shift();
|
|
1510
|
+
}
|
|
1511
|
+
if (sentAtMs < 0)
|
|
1512
|
+
return;
|
|
1513
|
+
const nowMs = Date.now();
|
|
1514
|
+
const cutoffMs = nowMs - RTT_WINDOW_MS;
|
|
1515
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1516
|
+
this._rttSamples.shift();
|
|
1517
|
+
this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
|
|
1518
|
+
}
|
|
1354
1519
|
// -- IRpcObject --
|
|
1355
1520
|
reconnect() {
|
|
1356
1521
|
}
|
|
@@ -1575,11 +1740,7 @@ var RpcStream = class _RpcStream {
|
|
|
1575
1740
|
if (index > this._nextExpectedIndex) {
|
|
1576
1741
|
warnLog4?.log(`item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1577
1742
|
if (!this.allowReconnect) {
|
|
1578
|
-
this.
|
|
1579
|
-
this._completionError = new Error(
|
|
1580
|
-
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1581
|
-
);
|
|
1582
|
-
this._notifyConsumer();
|
|
1743
|
+
this._complete(this._gapError(index));
|
|
1583
1744
|
return;
|
|
1584
1745
|
}
|
|
1585
1746
|
this._sendAck(this._nextExpectedIndex, true);
|
|
@@ -1599,11 +1760,7 @@ var RpcStream = class _RpcStream {
|
|
|
1599
1760
|
if (index > this._nextExpectedIndex) {
|
|
1600
1761
|
warnLog4?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1601
1762
|
if (!this.allowReconnect) {
|
|
1602
|
-
this.
|
|
1603
|
-
this._completionError = new Error(
|
|
1604
|
-
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1605
|
-
);
|
|
1606
|
-
this._notifyConsumer();
|
|
1763
|
+
this._complete(this._gapError(index));
|
|
1607
1764
|
return;
|
|
1608
1765
|
}
|
|
1609
1766
|
this._sendAck(this._nextExpectedIndex, true);
|
|
@@ -1620,9 +1777,20 @@ var RpcStream = class _RpcStream {
|
|
|
1620
1777
|
/** Called by system call handler when the stream ends ($sys.End). */
|
|
1621
1778
|
onEnd(index, error) {
|
|
1622
1779
|
if (this._disposed || this._completed) return;
|
|
1623
|
-
this.
|
|
1624
|
-
|
|
1625
|
-
|
|
1780
|
+
if (index > this._nextExpectedIndex) {
|
|
1781
|
+
warnLog4?.log(`end index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1782
|
+
if (!this.allowReconnect) {
|
|
1783
|
+
this._complete(this._gapError(index));
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
this._sendAck(this._nextExpectedIndex, true);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
if (index < this._nextExpectedIndex) {
|
|
1790
|
+
this._maybeSendAck(Math.min(index, this._nextConsumedIndex));
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
this._complete(error);
|
|
1626
1794
|
}
|
|
1627
1795
|
// -- IRpcObject --
|
|
1628
1796
|
reconnect() {
|
|
@@ -1656,6 +1824,7 @@ var RpcStream = class _RpcStream {
|
|
|
1656
1824
|
next: async () => {
|
|
1657
1825
|
if (!this._started) {
|
|
1658
1826
|
this._started = true;
|
|
1827
|
+
this.peer.remoteObjects.register(this);
|
|
1659
1828
|
this._sendAck(0, true);
|
|
1660
1829
|
}
|
|
1661
1830
|
this._maybeSendAck(this._nextConsumedIndex);
|
|
@@ -1666,6 +1835,7 @@ var RpcStream = class _RpcStream {
|
|
|
1666
1835
|
return { value, done: false };
|
|
1667
1836
|
}
|
|
1668
1837
|
if (this._completed) {
|
|
1838
|
+
this.dispose();
|
|
1669
1839
|
if (this._completionError) throw this._completionError;
|
|
1670
1840
|
return { value: void 0, done: true };
|
|
1671
1841
|
}
|
|
@@ -1674,7 +1844,8 @@ var RpcStream = class _RpcStream {
|
|
|
1674
1844
|
}
|
|
1675
1845
|
},
|
|
1676
1846
|
return: () => {
|
|
1677
|
-
this.
|
|
1847
|
+
if (this._started && !this._completed)
|
|
1848
|
+
this._sendAckEnd();
|
|
1678
1849
|
this.dispose();
|
|
1679
1850
|
return Promise.resolve({ value: void 0, done: true });
|
|
1680
1851
|
}
|
|
@@ -1690,6 +1861,22 @@ var RpcStream = class _RpcStream {
|
|
|
1690
1861
|
this._notifyConsumer();
|
|
1691
1862
|
}
|
|
1692
1863
|
// -- Private --
|
|
1864
|
+
// Mirrors .NET RpcStream.CloseFromLock: acknowledge the end to the sender
|
|
1865
|
+
// (so its RpcSharedStream is released) and unregister immediately.
|
|
1866
|
+
_complete(error) {
|
|
1867
|
+
if (this._completed) return;
|
|
1868
|
+
this._completed = true;
|
|
1869
|
+
this._completionError = error;
|
|
1870
|
+
this._sendAckEnd();
|
|
1871
|
+
if (this.kind === RpcObjectKind.Remote)
|
|
1872
|
+
this.peer.remoteObjects.unregister(this);
|
|
1873
|
+
this._notifyConsumer();
|
|
1874
|
+
}
|
|
1875
|
+
_gapError(index) {
|
|
1876
|
+
return new Error(
|
|
1877
|
+
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1693
1880
|
_notifyConsumer() {
|
|
1694
1881
|
if (this._consumerWaiting !== null) {
|
|
1695
1882
|
this._consumerWaiting.resolve();
|
|
@@ -1706,7 +1893,7 @@ var RpcStream = class _RpcStream {
|
|
|
1706
1893
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
1707
1894
|
_sendAck(nextIndex, mustReset) {
|
|
1708
1895
|
this._ackSentUpTo = nextIndex;
|
|
1709
|
-
const conn = this.peer.connection;
|
|
1896
|
+
const conn = this.peer.isConnected ? this.peer.connection : void 0;
|
|
1710
1897
|
if (conn) {
|
|
1711
1898
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
1712
1899
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -1719,7 +1906,7 @@ var RpcStream = class _RpcStream {
|
|
|
1719
1906
|
}
|
|
1720
1907
|
}
|
|
1721
1908
|
_sendAckEnd() {
|
|
1722
|
-
const conn = this.peer.connection;
|
|
1909
|
+
const conn = this.peer.isConnected ? this.peer.connection : void 0;
|
|
1723
1910
|
if (conn) {
|
|
1724
1911
|
this.peer.hub.systemCallSender.ackEnd(
|
|
1725
1912
|
conn,
|
|
@@ -1739,9 +1926,7 @@ function resolveStreamRefs(value, peer) {
|
|
|
1739
1926
|
if (typeof value === "string") {
|
|
1740
1927
|
const ref = parseStreamRef(value);
|
|
1741
1928
|
if (ref !== null) {
|
|
1742
|
-
|
|
1743
|
-
peer.remoteObjects.register(stream);
|
|
1744
|
-
return stream;
|
|
1929
|
+
return new RpcStream(ref, peer);
|
|
1745
1930
|
}
|
|
1746
1931
|
return value;
|
|
1747
1932
|
}
|
|
@@ -1794,6 +1979,11 @@ var RpcSystemCallHandler = class {
|
|
|
1794
1979
|
case RpcSystemCalls.ok: {
|
|
1795
1980
|
const call = peer.outboundCalls.get(relatedId);
|
|
1796
1981
|
if (call !== void 0) {
|
|
1982
|
+
if (args.length > 1) {
|
|
1983
|
+
peer.outboundCalls.remove(relatedId);
|
|
1984
|
+
call.result.reject(polymorphicPayloadError(method, args.length, 1));
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1797
1987
|
call.completedStage |= RpcCallStage.ResultReady;
|
|
1798
1988
|
if (call.removeOnOk) {
|
|
1799
1989
|
peer.outboundCalls.remove(relatedId);
|
|
@@ -1807,19 +1997,15 @@ var RpcSystemCallHandler = class {
|
|
|
1807
1997
|
break;
|
|
1808
1998
|
}
|
|
1809
1999
|
case RpcSystemCalls.error: {
|
|
1810
|
-
const call = peer.outboundCalls.
|
|
2000
|
+
const call = peer.outboundCalls.get(relatedId);
|
|
1811
2001
|
if (call !== void 0) {
|
|
2002
|
+
call.completedStage |= RpcCallStage.ResultReady;
|
|
2003
|
+
if (call.removeOnOk) {
|
|
2004
|
+
peer.outboundCalls.remove(relatedId);
|
|
2005
|
+
}
|
|
1812
2006
|
const errorInfo = args[0];
|
|
1813
2007
|
const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
|
|
1814
|
-
const
|
|
1815
|
-
let typeName;
|
|
1816
|
-
if (typeof errorType === "string") {
|
|
1817
|
-
const commaIdx = errorType.indexOf(",");
|
|
1818
|
-
typeName = commaIdx >= 0 ? errorType.slice(0, commaIdx) : errorType;
|
|
1819
|
-
} else if (errorType !== null && typeof errorType === "object") {
|
|
1820
|
-
const t = errorType;
|
|
1821
|
-
typeName = t.TypeName ?? t.typeName;
|
|
1822
|
-
}
|
|
2008
|
+
const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);
|
|
1823
2009
|
if (typeName === "ActualLab.Rpc.RpcRerouteException")
|
|
1824
2010
|
warnLog5?.log("Got RpcRerouteException from remote peer:", msg);
|
|
1825
2011
|
call.result.reject(new RpcError(msg, typeName));
|
|
@@ -1827,17 +2013,29 @@ var RpcSystemCallHandler = class {
|
|
|
1827
2013
|
break;
|
|
1828
2014
|
}
|
|
1829
2015
|
case RpcSystemCalls.cancel: {
|
|
1830
|
-
peer.inboundCalls.remove(relatedId);
|
|
2016
|
+
const call = peer.inboundCalls.remove(relatedId);
|
|
2017
|
+
call?.cancel();
|
|
1831
2018
|
break;
|
|
1832
2019
|
}
|
|
1833
2020
|
case RpcSystemCalls.keepAlive: {
|
|
1834
2021
|
peer.notifyKeepAliveReceived();
|
|
2022
|
+
const keepAliveIds = args[0];
|
|
2023
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.connection !== void 0) {
|
|
2024
|
+
const unknownIds = [];
|
|
2025
|
+
for (const id of keepAliveIds)
|
|
2026
|
+
if (peer.sharedObjects.get(id) === void 0)
|
|
2027
|
+
unknownIds.push(id);
|
|
2028
|
+
if (unknownIds.length > 0)
|
|
2029
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, unknownIds);
|
|
2030
|
+
}
|
|
1835
2031
|
break;
|
|
1836
2032
|
}
|
|
1837
2033
|
case RpcSystemCalls.item: {
|
|
1838
2034
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1839
2035
|
if (!stream)
|
|
1840
2036
|
debugLog?.log(`$sys.I: no stream for relatedId=${relatedId}`);
|
|
2037
|
+
else if (args.length > 2)
|
|
2038
|
+
stream.onEnd(args[0], polymorphicPayloadError(method, args.length, 2));
|
|
1841
2039
|
else
|
|
1842
2040
|
stream.onItem(
|
|
1843
2041
|
args[0],
|
|
@@ -1849,6 +2047,8 @@ var RpcSystemCallHandler = class {
|
|
|
1849
2047
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1850
2048
|
if (!stream)
|
|
1851
2049
|
debugLog?.log(`$sys.B: no stream for relatedId=${relatedId}`);
|
|
2050
|
+
else if (args.length > 2)
|
|
2051
|
+
stream.onEnd(args[0], polymorphicPayloadError(method, args.length, 2));
|
|
1852
2052
|
else if (Array.isArray(args[1])) {
|
|
1853
2053
|
const items = args[1];
|
|
1854
2054
|
for (let i = 0; i < items.length; i++)
|
|
@@ -1863,20 +2063,32 @@ var RpcSystemCallHandler = class {
|
|
|
1863
2063
|
debugLog?.log(`$sys.End: no stream for relatedId=${relatedId}`);
|
|
1864
2064
|
} else {
|
|
1865
2065
|
const errorInfo = args[1];
|
|
2066
|
+
const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);
|
|
1866
2067
|
const msg = errorInfo?.Message ?? errorInfo?.message;
|
|
1867
|
-
|
|
2068
|
+
let error = null;
|
|
2069
|
+
if (typeName || msg) {
|
|
2070
|
+
error = new Error(msg ?? typeName ?? "RPC stream error");
|
|
2071
|
+
if (typeName)
|
|
2072
|
+
error.name = typeName;
|
|
2073
|
+
}
|
|
1868
2074
|
stream.onEnd(args[0], error);
|
|
1869
2075
|
}
|
|
1870
2076
|
break;
|
|
1871
2077
|
}
|
|
1872
2078
|
case RpcSystemCalls.ack: {
|
|
1873
2079
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1874
|
-
sender
|
|
2080
|
+
if (sender)
|
|
2081
|
+
sender.onAck(args[0], args[1]);
|
|
2082
|
+
else if (peer.connection !== void 0)
|
|
2083
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);
|
|
1875
2084
|
break;
|
|
1876
2085
|
}
|
|
1877
2086
|
case RpcSystemCalls.ackEnd: {
|
|
1878
2087
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1879
|
-
sender
|
|
2088
|
+
if (sender)
|
|
2089
|
+
sender.onAckEnd(args[0]);
|
|
2090
|
+
else if (peer.connection !== void 0)
|
|
2091
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);
|
|
1880
2092
|
break;
|
|
1881
2093
|
}
|
|
1882
2094
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1910,19 +2122,34 @@ var RpcSystemCallHandler = class {
|
|
|
1910
2122
|
*/
|
|
1911
2123
|
_handleReconnect(relatedId, args, peer) {
|
|
1912
2124
|
if (peer.connection === void 0) return;
|
|
2125
|
+
const handshakeIndex = args[0];
|
|
2126
|
+
if (typeof handshakeIndex === "number" && handshakeIndex !== peer.ownHandshakeIndex) {
|
|
2127
|
+
peer.hub.systemCallSender.error(
|
|
2128
|
+
peer.connection,
|
|
2129
|
+
peer.serializationFormat,
|
|
2130
|
+
relatedId,
|
|
2131
|
+
new Error(
|
|
2132
|
+
`TooLateToReconnect: own handshake index ${peer.ownHandshakeIndex} != ${handshakeIndex}`
|
|
2133
|
+
)
|
|
2134
|
+
);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
1913
2137
|
let unknownIds = [];
|
|
1914
2138
|
try {
|
|
1915
2139
|
const stagesRaw = args[1];
|
|
1916
2140
|
if (stagesRaw && typeof stagesRaw === "object" && !Array.isArray(stagesRaw)) {
|
|
1917
2141
|
const unknownSet = /* @__PURE__ */ new Set();
|
|
1918
|
-
for (const [, rawValue] of Object.entries(stagesRaw)) {
|
|
2142
|
+
for (const [stageKey, rawValue] of Object.entries(stagesRaw)) {
|
|
1919
2143
|
const bytes = _toBytes(rawValue);
|
|
1920
2144
|
if (bytes === null) continue;
|
|
2145
|
+
const completedStage = parseInt(stageKey, 10) || 0;
|
|
1921
2146
|
const callIds = IncreasingSeqCompressor.deserialize(bytes);
|
|
1922
2147
|
for (const callId of callIds) {
|
|
1923
|
-
|
|
2148
|
+
const call = peer.inboundCalls.get(callId);
|
|
2149
|
+
if (call === void 0)
|
|
1924
2150
|
unknownSet.add(callId);
|
|
1925
|
-
|
|
2151
|
+
else if (completedStage < 1)
|
|
2152
|
+
call.resendResult();
|
|
1926
2153
|
}
|
|
1927
2154
|
}
|
|
1928
2155
|
unknownIds = [...unknownSet].sort((a, b) => a - b);
|
|
@@ -1934,6 +2161,29 @@ var RpcSystemCallHandler = class {
|
|
|
1934
2161
|
peer.hub.systemCallSender.ok(peer.connection, peer.serializationFormat, relatedId, responseValue);
|
|
1935
2162
|
}
|
|
1936
2163
|
};
|
|
2164
|
+
function polymorphicPayloadError(method, got, expected) {
|
|
2165
|
+
return new Error(
|
|
2166
|
+
`${method}: expected ${expected} value(s), got ${got} \u2014 polymorphic payloads are not supported by the TS client`
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
function extractTypeName(typeRef) {
|
|
2170
|
+
if (typeof typeRef === "string") {
|
|
2171
|
+
if (typeRef.length === 0)
|
|
2172
|
+
return void 0;
|
|
2173
|
+
const commaIdx = typeRef.indexOf(",");
|
|
2174
|
+
return commaIdx >= 0 ? typeRef.slice(0, commaIdx) : typeRef;
|
|
2175
|
+
}
|
|
2176
|
+
if (typeRef !== null && typeof typeRef === "object") {
|
|
2177
|
+
const t = typeRef;
|
|
2178
|
+
const aqn = t.AssemblyQualifiedName ?? t.assemblyQualifiedName;
|
|
2179
|
+
if (typeof aqn === "string" && aqn.length > 0) {
|
|
2180
|
+
const commaIdx = aqn.indexOf(",");
|
|
2181
|
+
return commaIdx >= 0 ? aqn.slice(0, commaIdx) : aqn;
|
|
2182
|
+
}
|
|
2183
|
+
return t.TypeName ?? t.typeName;
|
|
2184
|
+
}
|
|
2185
|
+
return void 0;
|
|
2186
|
+
}
|
|
1937
2187
|
function _toBytes(value) {
|
|
1938
2188
|
if (value instanceof Uint8Array) return value;
|
|
1939
2189
|
if (typeof value === "string") {
|
|
@@ -2017,12 +2267,11 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
2017
2267
|
}
|
|
2018
2268
|
}
|
|
2019
2269
|
error(conn, format, relatedId, error) {
|
|
2020
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2021
2270
|
this._send(
|
|
2022
2271
|
conn,
|
|
2023
2272
|
format,
|
|
2024
2273
|
{ Method: RpcSystemCalls.error, RelatedId: relatedId },
|
|
2025
|
-
[
|
|
2274
|
+
[toExceptionInfo(error)]
|
|
2026
2275
|
);
|
|
2027
2276
|
}
|
|
2028
2277
|
cancel(conn, format, relatedId) {
|
|
@@ -2036,6 +2285,14 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
2036
2285
|
activeCallIds
|
|
2037
2286
|
]);
|
|
2038
2287
|
}
|
|
2288
|
+
// Tells the remote peer the listed shared-object ids are gone on our side,
|
|
2289
|
+
// so its matching remote objects (e.g. RpcStream consumers) fail fast
|
|
2290
|
+
// instead of hanging. Mirrors .NET RpcSystemCallSender.Disconnect.
|
|
2291
|
+
disconnect(conn, format, localIds) {
|
|
2292
|
+
this._send(conn, format, { Method: RpcSystemCalls.disconnect }, [
|
|
2293
|
+
localIds
|
|
2294
|
+
]);
|
|
2295
|
+
}
|
|
2039
2296
|
item(conn, format, localId, index, item) {
|
|
2040
2297
|
this._itemEnv.RelatedId = localId;
|
|
2041
2298
|
this._itemArgs[0] = index;
|
|
@@ -2073,7 +2330,13 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
2073
2330
|
var RpcRemoteObjectTracker = class {
|
|
2074
2331
|
_objects = /* @__PURE__ */ new Map();
|
|
2075
2332
|
register(obj) {
|
|
2076
|
-
|
|
2333
|
+
const localId = obj.id.localId;
|
|
2334
|
+
const existing = this._objects.get(localId);
|
|
2335
|
+
if (existing === obj)
|
|
2336
|
+
return;
|
|
2337
|
+
if (existing !== void 0)
|
|
2338
|
+
existing.disconnect();
|
|
2339
|
+
this._objects.set(localId, obj);
|
|
2077
2340
|
}
|
|
2078
2341
|
get(localId) {
|
|
2079
2342
|
return this._objects.get(localId);
|
|
@@ -2082,7 +2345,9 @@ var RpcRemoteObjectTracker = class {
|
|
|
2082
2345
|
return this._objects.keys();
|
|
2083
2346
|
}
|
|
2084
2347
|
unregister(obj) {
|
|
2085
|
-
|
|
2348
|
+
const localId = obj.id.localId;
|
|
2349
|
+
if (this._objects.get(localId) === obj)
|
|
2350
|
+
this._objects.delete(localId);
|
|
2086
2351
|
}
|
|
2087
2352
|
disconnectAll() {
|
|
2088
2353
|
for (const obj of this._objects.values()) {
|
|
@@ -2106,7 +2371,10 @@ var RpcSharedObjectTracker = class {
|
|
|
2106
2371
|
return this._nextId++;
|
|
2107
2372
|
}
|
|
2108
2373
|
register(obj) {
|
|
2109
|
-
|
|
2374
|
+
const localId = obj.id.localId;
|
|
2375
|
+
if (this._objects.has(localId))
|
|
2376
|
+
throw new Error(`RPC shared object with localId ${localId} is already registered.`);
|
|
2377
|
+
this._objects.set(localId, obj);
|
|
2110
2378
|
}
|
|
2111
2379
|
get(localId) {
|
|
2112
2380
|
return this._objects.get(localId);
|
|
@@ -2115,7 +2383,9 @@ var RpcSharedObjectTracker = class {
|
|
|
2115
2383
|
return this._objects.keys();
|
|
2116
2384
|
}
|
|
2117
2385
|
unregister(obj) {
|
|
2118
|
-
|
|
2386
|
+
const localId = obj.id.localId;
|
|
2387
|
+
if (this._objects.get(localId) === obj)
|
|
2388
|
+
this._objects.delete(localId);
|
|
2119
2389
|
}
|
|
2120
2390
|
disconnectAll() {
|
|
2121
2391
|
for (const obj of this._objects.values()) {
|
|
@@ -2145,9 +2415,7 @@ var RpcSerializationFormat = class {
|
|
|
2145
2415
|
static NewtonsoftJsonV5;
|
|
2146
2416
|
static NewtonsoftJsonV5NP;
|
|
2147
2417
|
static MessagePackV6;
|
|
2148
|
-
static MemoryPackV6;
|
|
2149
2418
|
static MessagePackV6C;
|
|
2150
|
-
static MemoryPackV6C;
|
|
2151
2419
|
static All;
|
|
2152
2420
|
/** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */
|
|
2153
2421
|
static get(key) {
|
|
@@ -2243,18 +2511,14 @@ RpcSerializationFormat.SystemJsonV5NP = new RpcJsonSerializationFormat("json5np"
|
|
|
2243
2511
|
RpcSerializationFormat.NewtonsoftJsonV5 = new RpcJsonSerializationFormat("njson5");
|
|
2244
2512
|
RpcSerializationFormat.NewtonsoftJsonV5NP = new RpcJsonSerializationFormat("njson5np");
|
|
2245
2513
|
RpcSerializationFormat.MessagePackV6 = new RpcMessagePackSerializationFormat("msgpack6");
|
|
2246
|
-
RpcSerializationFormat.MemoryPackV6 = new RpcMessagePackSerializationFormat("mempack6");
|
|
2247
2514
|
RpcSerializationFormat.MessagePackV6C = new RpcMessagePackCompactSerializationFormat("msgpack6c");
|
|
2248
|
-
RpcSerializationFormat.MemoryPackV6C = new RpcMessagePackCompactSerializationFormat("mempack6c");
|
|
2249
2515
|
RpcSerializationFormat.All = [
|
|
2250
2516
|
RpcSerializationFormat.SystemJsonV5,
|
|
2251
2517
|
RpcSerializationFormat.SystemJsonV5NP,
|
|
2252
2518
|
RpcSerializationFormat.NewtonsoftJsonV5,
|
|
2253
2519
|
RpcSerializationFormat.NewtonsoftJsonV5NP,
|
|
2254
2520
|
RpcSerializationFormat.MessagePackV6,
|
|
2255
|
-
RpcSerializationFormat.
|
|
2256
|
-
RpcSerializationFormat.MessagePackV6C,
|
|
2257
|
-
RpcSerializationFormat.MemoryPackV6C
|
|
2521
|
+
RpcSerializationFormat.MessagePackV6C
|
|
2258
2522
|
];
|
|
2259
2523
|
var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
2260
2524
|
static DefaultFormats = RpcSerializationFormat.All;
|
|
@@ -2274,6 +2538,8 @@ var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
|
2274
2538
|
this.formats = map;
|
|
2275
2539
|
}
|
|
2276
2540
|
get(key) {
|
|
2541
|
+
if (isMemoryPackKey(key))
|
|
2542
|
+
throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);
|
|
2277
2543
|
const format = this.tryGet(key);
|
|
2278
2544
|
if (!format)
|
|
2279
2545
|
throw new Error(`No format with key '${key}'.`);
|
|
@@ -2281,9 +2547,14 @@ var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
|
2281
2547
|
}
|
|
2282
2548
|
tryGet(key) {
|
|
2283
2549
|
if (!key) key = this.defaultFormatKey;
|
|
2550
|
+
if (isMemoryPackKey(key))
|
|
2551
|
+
throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);
|
|
2284
2552
|
return this.formats.get(key);
|
|
2285
2553
|
}
|
|
2286
2554
|
};
|
|
2555
|
+
function isMemoryPackKey(key) {
|
|
2556
|
+
return key.startsWith("mempack");
|
|
2557
|
+
}
|
|
2287
2558
|
|
|
2288
2559
|
// src/rpc-peer.ts
|
|
2289
2560
|
function yieldToEventLoop() {
|
|
@@ -2337,6 +2608,10 @@ var RpcPeer = class {
|
|
|
2337
2608
|
* attempt (the reconnect delay is part of this phase); `Connected` is
|
|
2338
2609
|
* entered only after the handshake has succeeded. */
|
|
2339
2610
|
connectionStateChanged = new import_core7.EventHandlerSet();
|
|
2611
|
+
/** Index of the handshake THIS peer most recently sent. Used to reject a
|
|
2612
|
+
* stale `$sys.Reconnect` that references an older connection generation
|
|
2613
|
+
* (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */
|
|
2614
|
+
_ownHandshakeIndex = 0;
|
|
2340
2615
|
_connection;
|
|
2341
2616
|
/** Mirror of `_connectionState === Connected`. True only after the
|
|
2342
2617
|
* handshake round-trip completes; outbound `call()` checks this to
|
|
@@ -2348,6 +2623,7 @@ var RpcPeer = class {
|
|
|
2348
2623
|
_connectionState = 0 /* Disconnected */;
|
|
2349
2624
|
_keepAliveTimer;
|
|
2350
2625
|
_keepAliveWatchdog;
|
|
2626
|
+
_maintainTimer;
|
|
2351
2627
|
/** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
|
|
2352
2628
|
* for the current connection. Useful for diagnostics. */
|
|
2353
2629
|
_lastKeepAliveAt = 0;
|
|
@@ -2362,6 +2638,37 @@ var RpcPeer = class {
|
|
|
2362
2638
|
this.ref = ref;
|
|
2363
2639
|
this.keepAlivePeriodMs = hub.limits.keepAlivePeriodMs;
|
|
2364
2640
|
this.keepAliveTimeoutMs = hub.limits.keepAliveTimeoutMs;
|
|
2641
|
+
this.inboundCalls.completedCallsLimit = hub.limits.completedInboundCallsLimit;
|
|
2642
|
+
const checkPeriod = hub.limits.callTimeoutCheckPeriodMs;
|
|
2643
|
+
if (checkPeriod > 0 && Number.isFinite(checkPeriod)) {
|
|
2644
|
+
this._maintainTimer = setInterval(() => this._maintainOutboundCalls(), checkPeriod);
|
|
2645
|
+
this._maintainTimer.unref?.();
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
/** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
|
|
2649
|
+
* the connect timeout while a call still awaits the wire, the run timeout
|
|
2650
|
+
* once it has been sent. Mirrors .NET `RpcOutboundCallTracker.Maintain`
|
|
2651
|
+
* (RpcCallTrackers.cs:108-207). Calls without timeouts (queries) never
|
|
2652
|
+
* time out here. */
|
|
2653
|
+
_maintainOutboundCalls() {
|
|
2654
|
+
const now = Date.now();
|
|
2655
|
+
const canRunTimeout = this._isConnected && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);
|
|
2656
|
+
for (const call of [...this.outboundCalls.values()]) {
|
|
2657
|
+
const timeouts = call.timeouts;
|
|
2658
|
+
if (timeouts === void 0 || call.result.isCompleted)
|
|
2659
|
+
continue;
|
|
2660
|
+
let timedOut;
|
|
2661
|
+
if (call.sentAt === 0) {
|
|
2662
|
+
if (now - call.startedAt >= timeouts.connectTimeoutMs)
|
|
2663
|
+
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2664
|
+
} else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)
|
|
2665
|
+
timedOut = `run timeout (${timeouts.runTimeoutMs}ms)`;
|
|
2666
|
+
if (timedOut === void 0)
|
|
2667
|
+
continue;
|
|
2668
|
+
this.outboundCalls.remove(call.callId);
|
|
2669
|
+
call.result.reject(new import_core7.TimeoutError(`'${this.ref}': call ${call.method}#${call.callId} timed out \u2014 ${timedOut}`));
|
|
2670
|
+
call.onDisconnect();
|
|
2671
|
+
}
|
|
2365
2672
|
}
|
|
2366
2673
|
get connection() {
|
|
2367
2674
|
return this._connection;
|
|
@@ -2372,6 +2679,9 @@ var RpcPeer = class {
|
|
|
2372
2679
|
get isConnected() {
|
|
2373
2680
|
return this._isConnected;
|
|
2374
2681
|
}
|
|
2682
|
+
get ownHandshakeIndex() {
|
|
2683
|
+
return this._ownHandshakeIndex;
|
|
2684
|
+
}
|
|
2375
2685
|
_setConnectionState(value) {
|
|
2376
2686
|
if (this._connectionState === value) return;
|
|
2377
2687
|
const previousState = this._connectionState;
|
|
@@ -2443,14 +2753,17 @@ var RpcPeer = class {
|
|
|
2443
2753
|
this._connection?.encoder,
|
|
2444
2754
|
this.hub.registry
|
|
2445
2755
|
);
|
|
2756
|
+
outboundCall.timeouts = options?.timeouts;
|
|
2446
2757
|
this.outboundCalls.register(outboundCall);
|
|
2447
2758
|
if (this._isConnected) {
|
|
2759
|
+
outboundCall.sentAt = Date.now();
|
|
2448
2760
|
this._sendWireData(outboundCall.serializedWireData);
|
|
2449
2761
|
} else {
|
|
2450
2762
|
const stateHandler = (state) => {
|
|
2451
2763
|
if (state !== 3 /* Connected */) return;
|
|
2452
2764
|
this.connectionStateChanged.remove(stateHandler);
|
|
2453
2765
|
if (outboundCall.result.isCompleted) return;
|
|
2766
|
+
outboundCall.sentAt = Date.now();
|
|
2454
2767
|
this._sendWireData(outboundCall.serializedWireData);
|
|
2455
2768
|
};
|
|
2456
2769
|
this.connectionStateChanged.add(stateHandler);
|
|
@@ -2460,9 +2773,9 @@ var RpcPeer = class {
|
|
|
2460
2773
|
if (signal !== void 0) {
|
|
2461
2774
|
const onAbort = () => {
|
|
2462
2775
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2463
|
-
outboundCall.result.reject(
|
|
2776
|
+
outboundCall.result.reject((0, import_core7.cancellationError)("Call cancelled."));
|
|
2464
2777
|
outboundCall.onDisconnect();
|
|
2465
|
-
if (this._connection !== void 0)
|
|
2778
|
+
if (this._isConnected && this._connection !== void 0)
|
|
2466
2779
|
this.hub.systemCallSender.cancel(this._connection, this.serializationFormat, callId);
|
|
2467
2780
|
}
|
|
2468
2781
|
};
|
|
@@ -2492,10 +2805,15 @@ var RpcPeer = class {
|
|
|
2492
2805
|
}
|
|
2493
2806
|
close() {
|
|
2494
2807
|
infoLog?.log(`'${this.ref}': Stopping`);
|
|
2808
|
+
if (this._maintainTimer !== void 0) {
|
|
2809
|
+
clearInterval(this._maintainTimer);
|
|
2810
|
+
this._maintainTimer = void 0;
|
|
2811
|
+
}
|
|
2495
2812
|
this.remoteObjects.disconnectAll();
|
|
2496
2813
|
this.sharedObjects.disconnectAll();
|
|
2497
|
-
this.outboundCalls.rejectAll(
|
|
2814
|
+
this.outboundCalls.rejectAll((0, import_core7.cancellationError)("Peer closed."));
|
|
2498
2815
|
this.outboundCalls.invalidateAll();
|
|
2816
|
+
this.inboundCalls.clear();
|
|
2499
2817
|
this._connection?.close();
|
|
2500
2818
|
this.hub.peers.delete(this.ref);
|
|
2501
2819
|
this._setConnectionState(0 /* Disconnected */);
|
|
@@ -2559,57 +2877,85 @@ var RpcPeer = class {
|
|
|
2559
2877
|
_handleInbound(message, args) {
|
|
2560
2878
|
const method = message.Method ?? "";
|
|
2561
2879
|
const relatedId = message.RelatedId ?? 0;
|
|
2562
|
-
const call = new RpcInboundCall(relatedId, method, args);
|
|
2563
2880
|
const methodDef = this.hub.serviceHost.getMethodDef(method);
|
|
2564
2881
|
const isNoWait = methodDef?.noWait === true;
|
|
2565
|
-
if (!isNoWait) {
|
|
2566
|
-
this.inboundCalls.register(call);
|
|
2567
|
-
}
|
|
2568
2882
|
const serviceHost = this.hub.serviceHost;
|
|
2569
|
-
|
|
2883
|
+
const dispatch = async (call2) => {
|
|
2570
2884
|
try {
|
|
2571
2885
|
const context = this._connection !== void 0 ? {
|
|
2572
2886
|
__rpcDispatch: true,
|
|
2573
2887
|
callId: relatedId,
|
|
2574
|
-
|
|
2888
|
+
callType: message.CallType ?? 0,
|
|
2889
|
+
connection: this._connection,
|
|
2890
|
+
signal: call2?.signal,
|
|
2891
|
+
peer: this
|
|
2575
2892
|
} : void 0;
|
|
2576
|
-
const result = await serviceHost.dispatch(
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2893
|
+
const result = await serviceHost.dispatch(method, args, context);
|
|
2894
|
+
if (methodDef?.stream === true) {
|
|
2895
|
+
if (this._connection !== void 0) {
|
|
2896
|
+
const stream = result instanceof RpcStream ? result : new RpcStream(result);
|
|
2897
|
+
const ref = stream.toRef(this);
|
|
2898
|
+
const send = () => {
|
|
2899
|
+
if (call2?.isCancelled === true) return;
|
|
2900
|
+
if (this._connection !== void 0)
|
|
2901
|
+
this.hub.systemCallSender.ok(
|
|
2902
|
+
this._connection,
|
|
2903
|
+
this.serializationFormat,
|
|
2904
|
+
relatedId,
|
|
2905
|
+
ref
|
|
2906
|
+
);
|
|
2907
|
+
};
|
|
2908
|
+
if (call2 !== void 0) {
|
|
2909
|
+
call2.setResult(send);
|
|
2910
|
+
this.inboundCalls.markCompleted(call2);
|
|
2911
|
+
}
|
|
2912
|
+
send();
|
|
2913
|
+
}
|
|
2914
|
+
} else if (call2 !== void 0) {
|
|
2915
|
+
const send = () => {
|
|
2916
|
+
if (call2.isCancelled) return;
|
|
2917
|
+
if (this._connection !== void 0)
|
|
2918
|
+
this.hub.systemCallSender.ok(
|
|
2919
|
+
this._connection,
|
|
2920
|
+
this.serializationFormat,
|
|
2921
|
+
relatedId,
|
|
2922
|
+
result
|
|
2923
|
+
);
|
|
2924
|
+
};
|
|
2925
|
+
call2.setResult(send);
|
|
2926
|
+
this.inboundCalls.markCompleted(call2);
|
|
2927
|
+
send();
|
|
2596
2928
|
}
|
|
2597
2929
|
} catch (e) {
|
|
2598
|
-
if (
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
this.
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2930
|
+
if (call2 !== void 0) {
|
|
2931
|
+
const send = () => {
|
|
2932
|
+
if (call2.isCancelled) return;
|
|
2933
|
+
if (this._connection !== void 0)
|
|
2934
|
+
this.hub.systemCallSender.error(
|
|
2935
|
+
this._connection,
|
|
2936
|
+
this.serializationFormat,
|
|
2937
|
+
relatedId,
|
|
2938
|
+
e
|
|
2939
|
+
);
|
|
2940
|
+
};
|
|
2941
|
+
call2.setResult(send);
|
|
2942
|
+
this.inboundCalls.markCompleted(call2);
|
|
2943
|
+
send();
|
|
2605
2944
|
}
|
|
2606
2945
|
debugLog2?.log(`'${this.ref}': Inbound call failed: ${method}#${relatedId}`, e);
|
|
2607
|
-
} finally {
|
|
2608
|
-
if (!isNoWait) {
|
|
2609
|
-
this.inboundCalls.remove(relatedId);
|
|
2610
|
-
}
|
|
2611
2946
|
}
|
|
2612
|
-
}
|
|
2947
|
+
};
|
|
2948
|
+
if (isNoWait) {
|
|
2949
|
+
void dispatch(void 0);
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
const call = new RpcInboundCall(relatedId, method, args);
|
|
2953
|
+
const existing = this.inboundCalls.getOrRegister(call);
|
|
2954
|
+
if (existing !== call) {
|
|
2955
|
+
existing.resendResult();
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
void dispatch(call);
|
|
2613
2959
|
}
|
|
2614
2960
|
/** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.
|
|
2615
2961
|
* Called when the peer transitions into `Connected` (after handshake).
|
|
@@ -2665,15 +3011,17 @@ var RpcConnectionState = /* @__PURE__ */ ((RpcConnectionState2) => {
|
|
|
2665
3011
|
})(RpcConnectionState || {});
|
|
2666
3012
|
var RpcClientPeer = class extends RpcPeer {
|
|
2667
3013
|
_disposed = false;
|
|
3014
|
+
/** Aborted when the peer is stopped/disposed — threaded into the reconnect
|
|
3015
|
+
* delay so a pending delay rejects promptly instead of parking. */
|
|
3016
|
+
_stopController = new AbortController();
|
|
2668
3017
|
_tryIndex = 0;
|
|
2669
|
-
_handshakeIndex = 0;
|
|
2670
3018
|
/** Index from the MOST RECENTLY RECEIVED server handshake — sent back
|
|
2671
3019
|
* to the server in `$sys.Reconnect:3` so the server can verify we're
|
|
2672
3020
|
* talking about the current connection. Mirrors .NET's
|
|
2673
3021
|
* `connectionState.Handshake.Index` path in
|
|
2674
3022
|
* `RpcOutboundCallTracker.TryReconnect`. */
|
|
2675
3023
|
_remoteHandshakeIndex = 0;
|
|
2676
|
-
|
|
3024
|
+
_lastRemotePeerId;
|
|
2677
3025
|
_pendingHandshake;
|
|
2678
3026
|
_reconnectsAt = 0;
|
|
2679
3027
|
/** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */
|
|
@@ -2694,6 +3042,10 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2694
3042
|
* the connection attempt. Initialized from `hub.limits` at construction;
|
|
2695
3043
|
* set directly to override on a single peer. */
|
|
2696
3044
|
handshakeTimeoutMs;
|
|
3045
|
+
/** A connection that lived less than this before dropping does NOT reset
|
|
3046
|
+
* `_tryIndex`, so a crash-looping server sees growing reconnect delays.
|
|
3047
|
+
* Initialized from `hub.limits` at construction; override per peer. */
|
|
3048
|
+
prematureDisconnectTimeoutMs;
|
|
2697
3049
|
peerChanged = new import_core7.EventHandlerSet();
|
|
2698
3050
|
/** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
|
|
2699
3051
|
unsupportedFormat = new import_core7.EventHandlerSet();
|
|
@@ -2726,6 +3078,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2726
3078
|
super(hub, url);
|
|
2727
3079
|
this.connectTimeoutMs = hub.limits.connectTimeoutMs;
|
|
2728
3080
|
this.handshakeTimeoutMs = hub.limits.handshakeTimeoutMs;
|
|
3081
|
+
this.prematureDisconnectTimeoutMs = hub.limits.prematureDisconnectTimeoutMs;
|
|
2729
3082
|
this.clientId = guidToBase64Url(this.id);
|
|
2730
3083
|
const resolver = RpcSerializationFormatResolver.Default;
|
|
2731
3084
|
this.serializationFormat = resolver.get(parseFormatFromUrl(url) ?? resolver.defaultFormatKey);
|
|
@@ -2762,7 +3115,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2762
3115
|
try {
|
|
2763
3116
|
while (!this._disposed) {
|
|
2764
3117
|
this._setConnectionState(1 /* Connecting */);
|
|
2765
|
-
const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex);
|
|
3118
|
+
const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex, this._stopController.signal);
|
|
2766
3119
|
if (retryDelay.isLimitExceeded) {
|
|
2767
3120
|
warnLog6?.log(`'${this.ref}': Reconnect limit exceeded - shutting down`);
|
|
2768
3121
|
this._disposed = true;
|
|
@@ -2771,12 +3124,14 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2771
3124
|
this._setReconnectsAt(retryDelay.endsAt);
|
|
2772
3125
|
try {
|
|
2773
3126
|
await retryDelay.promise;
|
|
3127
|
+
} catch {
|
|
2774
3128
|
} finally {
|
|
2775
3129
|
this._setReconnectsAt(0);
|
|
2776
3130
|
}
|
|
2777
3131
|
if (this._disposed) break;
|
|
2778
3132
|
let lastCloseCode = 0;
|
|
2779
3133
|
let lastCloseReason = "";
|
|
3134
|
+
let connectedAt = 0;
|
|
2780
3135
|
try {
|
|
2781
3136
|
const connUrl = await this.connectionUrlResolver(this);
|
|
2782
3137
|
infoLog?.log(`'${this.ref}': Connecting to ${sanitizeUrl(connUrl)}`);
|
|
@@ -2823,7 +3178,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2823
3178
|
this.serializationFormat,
|
|
2824
3179
|
this.id,
|
|
2825
3180
|
this.hub.hubId,
|
|
2826
|
-
++this.
|
|
3181
|
+
++this._ownHandshakeIndex
|
|
2827
3182
|
);
|
|
2828
3183
|
} catch (e) {
|
|
2829
3184
|
warnLog6?.log(`'${this.ref}': Failed to send handshake`, e);
|
|
@@ -2847,20 +3202,10 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2847
3202
|
this._pendingHandshake = void 0;
|
|
2848
3203
|
}
|
|
2849
3204
|
this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
|
|
2850
|
-
const
|
|
2851
|
-
let isPeerChanged = false;
|
|
2852
|
-
if (remoteHubId !== void 0) {
|
|
2853
|
-
isPeerChanged = this._lastRemoteHubId !== void 0 && this._lastRemoteHubId !== remoteHubId;
|
|
2854
|
-
if (isPeerChanged) {
|
|
2855
|
-
this.inboundCalls.clear();
|
|
2856
|
-
infoLog?.log(`'${this.ref}': Peer changed`);
|
|
2857
|
-
this.peerChanged.trigger();
|
|
2858
|
-
}
|
|
2859
|
-
this._lastRemoteHubId = remoteHubId;
|
|
2860
|
-
}
|
|
3205
|
+
const isPeerChanged = this._detectPeerChange(remoteHandshake);
|
|
2861
3206
|
infoLog?.log(`'${this.ref}': Handshake succeeded, peerChanged=${isPeerChanged}`);
|
|
2862
3207
|
this._setConnectionState(3 /* Connected */);
|
|
2863
|
-
|
|
3208
|
+
connectedAt = Date.now();
|
|
2864
3209
|
await this._reconnect(isPeerChanged, closedController.signal);
|
|
2865
3210
|
await whenClosed;
|
|
2866
3211
|
} catch (e) {
|
|
@@ -2874,6 +3219,8 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2874
3219
|
this._disposed = true;
|
|
2875
3220
|
break;
|
|
2876
3221
|
}
|
|
3222
|
+
if (connectedAt !== 0 && Date.now() - connectedAt >= this.prematureDisconnectTimeoutMs)
|
|
3223
|
+
this._tryIndex = 0;
|
|
2877
3224
|
this._tryIndex++;
|
|
2878
3225
|
}
|
|
2879
3226
|
} finally {
|
|
@@ -2929,6 +3276,8 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2929
3276
|
continue;
|
|
2930
3277
|
}
|
|
2931
3278
|
eligible.push(call);
|
|
3279
|
+
if (call.sentAt !== 0)
|
|
3280
|
+
call.sentAt = Date.now();
|
|
2932
3281
|
}
|
|
2933
3282
|
let unknownIds = null;
|
|
2934
3283
|
if (!isPeerChanged && eligible.length > 0) {
|
|
@@ -2945,6 +3294,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2945
3294
|
if (unknownIds !== null && !unknownIds.has(call.callId)) {
|
|
2946
3295
|
continue;
|
|
2947
3296
|
}
|
|
3297
|
+
call.sentAt = Date.now();
|
|
2948
3298
|
this._sendWireData(call.serializedWireData);
|
|
2949
3299
|
resentSinceYield++;
|
|
2950
3300
|
if (resentSinceYield >= YIELD_BATCH) {
|
|
@@ -3010,30 +3360,80 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
3010
3360
|
return new Set(eligible.map((c) => c.callId));
|
|
3011
3361
|
}
|
|
3012
3362
|
}
|
|
3363
|
+
/** Detects a server-peer identity change by comparing the incoming
|
|
3364
|
+
* handshake's `RemotePeerId` with the last one — C#
|
|
3365
|
+
* `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not
|
|
3366
|
+
* `RemoteHubId` (an idle server peer can be replaced while keeping the
|
|
3367
|
+
* same hub id). On a change it clears the inbound tracker and fires
|
|
3368
|
+
* `peerChanged`; the very first handshake is never a change. */
|
|
3369
|
+
_detectPeerChange(handshake) {
|
|
3370
|
+
const remotePeerId = handshake.RemotePeerId;
|
|
3371
|
+
if (remotePeerId === void 0)
|
|
3372
|
+
return false;
|
|
3373
|
+
const isPeerChanged = this._lastRemotePeerId !== void 0 && this._lastRemotePeerId !== remotePeerId;
|
|
3374
|
+
this._lastRemotePeerId = remotePeerId;
|
|
3375
|
+
if (isPeerChanged) {
|
|
3376
|
+
this.inboundCalls.clear();
|
|
3377
|
+
infoLog?.log(`'${this.ref}': Peer changed`);
|
|
3378
|
+
this.peerChanged.trigger();
|
|
3379
|
+
}
|
|
3380
|
+
return isPeerChanged;
|
|
3381
|
+
}
|
|
3013
3382
|
_onHandshakeReceived(handshake) {
|
|
3014
3383
|
this._pendingHandshake?.resolve(handshake);
|
|
3015
3384
|
}
|
|
3016
3385
|
close() {
|
|
3017
3386
|
this._disposed = true;
|
|
3387
|
+
this._stopController.abort();
|
|
3018
3388
|
super.close();
|
|
3019
3389
|
}
|
|
3020
3390
|
};
|
|
3021
3391
|
var RpcServerPeer = class extends RpcPeer {
|
|
3022
|
-
|
|
3023
|
-
|
|
3392
|
+
/** Grace period before an idle (disconnected) server peer is stopped and
|
|
3393
|
+
* removed from `hub.peers`; a re-`accept()` within it cancels removal.
|
|
3394
|
+
* Snapshotted from `hub.limits` at construction (F11). */
|
|
3395
|
+
serverPeerCloseTimeoutMs;
|
|
3396
|
+
_closeTimer;
|
|
3397
|
+
_isClosed = false;
|
|
3024
3398
|
constructor(hub, ref) {
|
|
3025
3399
|
super(hub, ref);
|
|
3400
|
+
this.serverPeerCloseTimeoutMs = hub.limits.serverPeerCloseTimeoutMs;
|
|
3026
3401
|
}
|
|
3027
3402
|
/** Accept an incoming connection — sets up message handling and handshake response. */
|
|
3028
3403
|
accept(conn) {
|
|
3404
|
+
this._clearCloseTimer();
|
|
3029
3405
|
this._setConnectionState(1 /* Connecting */);
|
|
3030
3406
|
this.setupConnection(conn);
|
|
3031
3407
|
this._setConnectionState(2 /* Handshaking */);
|
|
3032
3408
|
conn.closed.add(() => {
|
|
3033
3409
|
if (this._connection !== conn && this._connection !== void 0) return;
|
|
3034
3410
|
this.sharedObjects.disconnectAll();
|
|
3411
|
+
this._armCloseTimer();
|
|
3035
3412
|
});
|
|
3036
3413
|
}
|
|
3414
|
+
close() {
|
|
3415
|
+
this._isClosed = true;
|
|
3416
|
+
this._clearCloseTimer();
|
|
3417
|
+
super.close();
|
|
3418
|
+
}
|
|
3419
|
+
_armCloseTimer() {
|
|
3420
|
+
this._clearCloseTimer();
|
|
3421
|
+
if (this._isClosed) return;
|
|
3422
|
+
const timeout = this.serverPeerCloseTimeoutMs;
|
|
3423
|
+
if (!(timeout > 0) || !Number.isFinite(timeout)) return;
|
|
3424
|
+
this._closeTimer = setTimeout(() => {
|
|
3425
|
+
this._closeTimer = void 0;
|
|
3426
|
+
if (this._connection === void 0)
|
|
3427
|
+
this.close();
|
|
3428
|
+
}, timeout);
|
|
3429
|
+
this._closeTimer.unref?.();
|
|
3430
|
+
}
|
|
3431
|
+
_clearCloseTimer() {
|
|
3432
|
+
if (this._closeTimer !== void 0) {
|
|
3433
|
+
clearTimeout(this._closeTimer);
|
|
3434
|
+
this._closeTimer = void 0;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3037
3437
|
_onHandshakeReceived(_handshake) {
|
|
3038
3438
|
if (this._connection !== void 0) {
|
|
3039
3439
|
this.hub.systemCallSender.handshake(
|
|
@@ -3041,7 +3441,7 @@ var RpcServerPeer = class extends RpcPeer {
|
|
|
3041
3441
|
this.serializationFormat,
|
|
3042
3442
|
this.id,
|
|
3043
3443
|
this.hub.hubId,
|
|
3044
|
-
|
|
3444
|
+
++this._ownHandshakeIndex
|
|
3045
3445
|
);
|
|
3046
3446
|
this._setConnectionState(3 /* Connected */);
|
|
3047
3447
|
}
|
|
@@ -3080,6 +3480,12 @@ var RpcLimits = class _RpcLimits {
|
|
|
3080
3480
|
/** Process-wide default. Hubs read from this when they're constructed
|
|
3081
3481
|
* unless caller assigns a custom instance to `hub.limits`. */
|
|
3082
3482
|
static Default = new _RpcLimits();
|
|
3483
|
+
/** Dev preset with a relaxed keep-alive timeout (5 min), matching .NET's
|
|
3484
|
+
* debugger-attached `KeepAlivePeriod` (RpcLimits.cs:46-54). Activate
|
|
3485
|
+
* explicitly — e.g. `RpcLimits.Default = RpcLimits.Debug` or per-hub
|
|
3486
|
+
* `hub.limits = RpcLimits.Debug`. Never switched on automatically: the
|
|
3487
|
+
* TS side can't detect a debugger on the server (decision D4). */
|
|
3488
|
+
static Debug = new _RpcLimits({ keepAliveTimeoutMs: 3e5 });
|
|
3083
3489
|
/** Max time to wait for the WebSocket to enter the OPEN state. On a
|
|
3084
3490
|
* hung connect (mobile after network change, half-open after sleep)
|
|
3085
3491
|
* the browser can take ~2 min to emit `onerror`/`onclose`; without
|
|
@@ -3087,6 +3493,22 @@ var RpcLimits = class _RpcLimits {
|
|
|
3087
3493
|
connectTimeoutMs = 1e4;
|
|
3088
3494
|
/** Max time to wait for the server's handshake response after WS opens. */
|
|
3089
3495
|
handshakeTimeoutMs = 1e4;
|
|
3496
|
+
/** If a connection lived less than this before dropping, a graceful close
|
|
3497
|
+
* still counts as a failed attempt — the client keeps its growing
|
|
3498
|
+
* `_tryIndex` instead of resetting it, so a crash-looping server sees
|
|
3499
|
+
* increasing reconnect delays via `RetryDelaySeq`. Mirrors .NET
|
|
3500
|
+
* `RpcLimits.PrematureDisconnectTimeout` (RpcLimits.cs:18). */
|
|
3501
|
+
prematureDisconnectTimeoutMs = 15e3;
|
|
3502
|
+
/** Max completed inbound calls retained for duplicate-frame dedup
|
|
3503
|
+
* (a resent call id re-sends the computed result instead of
|
|
3504
|
+
* re-executing the handler). Deviation from .NET, which unregisters a
|
|
3505
|
+
* call once its result is sent: TS clients blind-resend on reconnect,
|
|
3506
|
+
* so completed calls are retained — bounded by this limit (oldest
|
|
3507
|
+
* evicted first) to cap memory on long-lived connections. */
|
|
3508
|
+
completedInboundCallsLimit = 1e3;
|
|
3509
|
+
/** How often each peer's maintenance loop scans outbound calls for
|
|
3510
|
+
* timeouts (R12). Mirrors .NET `RpcLimits.CallTimeoutCheckPeriod`. */
|
|
3511
|
+
callTimeoutCheckPeriodMs = 1e3;
|
|
3090
3512
|
/** Outbound `$sys.KeepAlive` send period. */
|
|
3091
3513
|
keepAlivePeriodMs = 1e4;
|
|
3092
3514
|
/** Reaper threshold — connection is force-closed (and the reconnect
|
|
@@ -3096,6 +3518,13 @@ var RpcLimits = class _RpcLimits {
|
|
|
3096
3518
|
* `keepAlivePeriodMs + stall_duration`, so with these defaults the
|
|
3097
3519
|
* tolerated stall is ~15 s. */
|
|
3098
3520
|
keepAliveTimeoutMs = 25e3;
|
|
3521
|
+
/** How long a server peer stays in `hub.peers` after its connection closes
|
|
3522
|
+
* before it's stopped and removed. A same-peer reconnect (`accept()`)
|
|
3523
|
+
* within this window cancels the pending removal, so brief drops don't
|
|
3524
|
+
* discard the peer's trackers. Mirrors .NET's server-peer shutdown timeout
|
|
3525
|
+
* (`RpcPeerOptions.ServerPeerShutdownTimeoutProvider`, 3-15 min); the TS
|
|
3526
|
+
* port uses a fixed floor-matching default. */
|
|
3527
|
+
serverPeerCloseTimeoutMs = 18e4;
|
|
3099
3528
|
constructor(overrides) {
|
|
3100
3529
|
if (overrides) Object.assign(this, overrides);
|
|
3101
3530
|
}
|
|
@@ -3106,7 +3535,7 @@ var import_core8 = require("@actuallab/core");
|
|
|
3106
3535
|
var RpcClientPeerReconnectDelayer = class extends import_core8.RetryDelayer {
|
|
3107
3536
|
constructor() {
|
|
3108
3537
|
super();
|
|
3109
|
-
this.delays = import_core8.RetryDelaySeq.
|
|
3538
|
+
this.delays = import_core8.RetryDelaySeq.exp(100, 1e4);
|
|
3110
3539
|
}
|
|
3111
3540
|
};
|
|
3112
3541
|
|
|
@@ -3114,13 +3543,14 @@ var RpcClientPeerReconnectDelayer = class extends import_core8.RetryDelayer {
|
|
|
3114
3543
|
var { warnLog: warnLog7 } = getLogs("RpcServiceHost");
|
|
3115
3544
|
var RpcServiceHost = class {
|
|
3116
3545
|
_methods = /* @__PURE__ */ new Map();
|
|
3117
|
-
register(def, impl) {
|
|
3546
|
+
register(def, impl, receiver = impl) {
|
|
3118
3547
|
for (const methodDef of def.methods.values()) {
|
|
3119
3548
|
const fn = impl[methodDef.name];
|
|
3120
3549
|
if (!fn) continue;
|
|
3121
3550
|
this._methods.set(wireMethodName(methodDef), {
|
|
3122
3551
|
def: methodDef,
|
|
3123
|
-
fn
|
|
3552
|
+
fn,
|
|
3553
|
+
receiver
|
|
3124
3554
|
});
|
|
3125
3555
|
}
|
|
3126
3556
|
}
|
|
@@ -3131,9 +3561,9 @@ var RpcServiceHost = class {
|
|
|
3131
3561
|
throw new Error(`Method not found: ${wireMethod}`);
|
|
3132
3562
|
}
|
|
3133
3563
|
if (context !== void 0 && entry.def.callTypeId !== 0) {
|
|
3134
|
-
return await entry.fn(...args, context);
|
|
3564
|
+
return await entry.fn.call(entry.receiver, ...args, context);
|
|
3135
3565
|
}
|
|
3136
|
-
return await entry.fn(...args);
|
|
3566
|
+
return await entry.fn.call(entry.receiver, ...args);
|
|
3137
3567
|
}
|
|
3138
3568
|
getMethodDef(wireMethod) {
|
|
3139
3569
|
return this._methods.get(wireMethod)?.def;
|
|
@@ -3141,6 +3571,7 @@ var RpcServiceHost = class {
|
|
|
3141
3571
|
};
|
|
3142
3572
|
|
|
3143
3573
|
// src/rpc-decorators.ts
|
|
3574
|
+
var import_core9 = require("@actuallab/core");
|
|
3144
3575
|
var METHODS_META = /* @__PURE__ */ Symbol.for("actuallab.methods");
|
|
3145
3576
|
var SERVICE_META = /* @__PURE__ */ Symbol.for("actuallab.service");
|
|
3146
3577
|
function getMethodsMeta(cls) {
|
|
@@ -3151,17 +3582,17 @@ function getServiceMeta(cls) {
|
|
|
3151
3582
|
}
|
|
3152
3583
|
function rpcService(serviceName) {
|
|
3153
3584
|
return function(_target, context) {
|
|
3154
|
-
const meta = context.metadata
|
|
3585
|
+
const meta = (0, import_core9.ownMetadata)(context.metadata, SERVICE_META);
|
|
3155
3586
|
meta.name = serviceName;
|
|
3156
3587
|
};
|
|
3157
3588
|
}
|
|
3158
3589
|
function rpcMethod(options) {
|
|
3159
3590
|
return function(target, context) {
|
|
3160
3591
|
const methodName = String(context.name);
|
|
3161
|
-
const methods = context.metadata
|
|
3592
|
+
const methods = (0, import_core9.ownMetadata)(context.metadata, METHODS_META);
|
|
3162
3593
|
methods[methodName] = {
|
|
3163
3594
|
...methods[methodName],
|
|
3164
|
-
argCount: target
|
|
3595
|
+
argCount: (0, import_core9.resolveArgCount)(target, options?.argCount, methodName),
|
|
3165
3596
|
returns: options?.returns,
|
|
3166
3597
|
remoteExecutionMode: options?.remoteExecutionMode
|
|
3167
3598
|
};
|
|
@@ -3707,7 +4138,7 @@ var RpcHub = class {
|
|
|
3707
4138
|
hubId;
|
|
3708
4139
|
peers = /* @__PURE__ */ new Map();
|
|
3709
4140
|
serviceHost;
|
|
3710
|
-
systemCallSender
|
|
4141
|
+
systemCallSender;
|
|
3711
4142
|
systemCallHandler = new RpcSystemCallHandler();
|
|
3712
4143
|
/** Method registry for compact format hash ↔ name resolution.
|
|
3713
4144
|
* Created lazily, populated by addService/addClient. */
|
|
@@ -3715,6 +4146,14 @@ var RpcHub = class {
|
|
|
3715
4146
|
/** Shared reconnect delayer used by every client peer in this hub. Swap in
|
|
3716
4147
|
* a custom subclass (e.g. app-level signal-gated) before peers start. */
|
|
3717
4148
|
reconnectDelayer = new RpcClientPeerReconnectDelayer();
|
|
4149
|
+
/** Cache of client proxies keyed by peer, then by service name — so
|
|
4150
|
+
* repeated {@link addClient} calls for the same service+peer return one
|
|
4151
|
+
* proxy. All consumers then share a single computed/call/invalidation
|
|
4152
|
+
* stream per logical value, matching .NET's singleton client proxies (F8).
|
|
4153
|
+
* A later def registered under the same name extends the existing proxy
|
|
4154
|
+
* with its extra methods (superset), so no consumer ever receives a proxy
|
|
4155
|
+
* missing methods the def declares. */
|
|
4156
|
+
_clientProxies = /* @__PURE__ */ new WeakMap();
|
|
3718
4157
|
/** Connection-lifecycle timing limits. Peers constructed against this hub
|
|
3719
4158
|
* read their initial `*Ms` field values from this instance; later
|
|
3720
4159
|
* mutations affect only peers built afterward (existing peers can be
|
|
@@ -3732,6 +4171,7 @@ var RpcHub = class {
|
|
|
3732
4171
|
constructor(hubId) {
|
|
3733
4172
|
this.hubId = hubId ?? crypto.randomUUID();
|
|
3734
4173
|
this.serviceHost = new RpcServiceHost();
|
|
4174
|
+
this.systemCallSender = this._createSystemCallSender();
|
|
3735
4175
|
this.systemCallSender.registry = this.registry;
|
|
3736
4176
|
for (const methodName of Object.values(RpcSystemCalls)) {
|
|
3737
4177
|
this.registry.register(methodName);
|
|
@@ -3792,50 +4232,38 @@ var RpcHub = class {
|
|
|
3792
4232
|
if (!fn) continue;
|
|
3793
4233
|
wrappedImpl[methodDef.name] = methodDef.callTypeId !== 0 ? this._wrapServerMethod(methodDef, fn, impl) : fn;
|
|
3794
4234
|
}
|
|
3795
|
-
this.serviceHost.register(def, wrappedImpl);
|
|
4235
|
+
this.serviceHost.register(def, wrappedImpl, impl);
|
|
3796
4236
|
this.registry.registerService(def.name, def.methods);
|
|
3797
4237
|
}
|
|
3798
|
-
/** Create a typed client proxy for a service on a remote peer.
|
|
4238
|
+
/** Create a typed client proxy for a service on a remote peer. Repeated calls
|
|
4239
|
+
* for the same (peer, service name) return one shared proxy; a def carrying
|
|
4240
|
+
* extra methods extends it (F8). */
|
|
3799
4241
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type
|
|
3800
4242
|
addClient(peer, defOrContract) {
|
|
3801
4243
|
const def = this._resolveServiceDef(defOrContract);
|
|
3802
4244
|
this.registry.registerService(def.name, def.methods);
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
byArgCount = /* @__PURE__ */ new Map();
|
|
3808
|
-
overloads.set(methodDef.name, byArgCount);
|
|
3809
|
-
}
|
|
3810
|
-
byArgCount.set(
|
|
3811
|
-
methodDef.argCount,
|
|
3812
|
-
this._createClientMethod(peer, methodDef)
|
|
3813
|
-
);
|
|
4245
|
+
let byService = this._clientProxies.get(peer);
|
|
4246
|
+
if (byService === void 0) {
|
|
4247
|
+
byService = /* @__PURE__ */ new Map();
|
|
4248
|
+
this._clientProxies.set(peer, byService);
|
|
3814
4249
|
}
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
methods.set(name, singleFn);
|
|
3820
|
-
} else {
|
|
3821
|
-
methods.set(name, (...args) => {
|
|
3822
|
-
const fn = byArgCount.get(args.length);
|
|
3823
|
-
if (!fn)
|
|
3824
|
-
throw new Error(
|
|
3825
|
-
`No overload of ${name} accepts ${args.length} arguments`
|
|
3826
|
-
);
|
|
3827
|
-
return fn(...args);
|
|
3828
|
-
});
|
|
3829
|
-
}
|
|
4250
|
+
let entry = byService.get(def.name);
|
|
4251
|
+
if (entry === void 0) {
|
|
4252
|
+
entry = this._createClientProxy();
|
|
4253
|
+
byService.set(def.name, entry);
|
|
3830
4254
|
}
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
});
|
|
4255
|
+
this._extendClientProxy(peer, entry, def);
|
|
4256
|
+
return entry.proxy;
|
|
3834
4257
|
}
|
|
3835
4258
|
close() {
|
|
3836
4259
|
for (const peer of this.peers.values()) peer.close();
|
|
3837
4260
|
this.peers.clear();
|
|
3838
4261
|
}
|
|
4262
|
+
/** Create the hub's system-call sender. Override in FusionHub to add
|
|
4263
|
+
* Fusion-specific system calls (e.g. $sys-c.Invalidate). */
|
|
4264
|
+
_createSystemCallSender() {
|
|
4265
|
+
return new RpcSystemCallSender();
|
|
4266
|
+
}
|
|
3839
4267
|
/** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */
|
|
3840
4268
|
_buildServiceDef(cls) {
|
|
3841
4269
|
const svcMeta = getServiceMeta(cls);
|
|
@@ -3893,12 +4321,10 @@ var RpcHub = class {
|
|
|
3893
4321
|
throw new Error(
|
|
3894
4322
|
`Expected stream reference, got: ${JSON.stringify(result)}`
|
|
3895
4323
|
);
|
|
3896
|
-
|
|
3897
|
-
peer.remoteObjects.register(stream);
|
|
3898
|
-
return stream;
|
|
4324
|
+
return new RpcStream(ref, peer);
|
|
3899
4325
|
};
|
|
3900
4326
|
}
|
|
3901
|
-
const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
4327
|
+
const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== void 0 ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts } : void 0;
|
|
3902
4328
|
return async (...args) => {
|
|
3903
4329
|
if (mustCheckConnection && !peer.isConnected)
|
|
3904
4330
|
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
@@ -3908,7 +4334,58 @@ var RpcHub = class {
|
|
|
3908
4334
|
return resolveStreamRefs(result, peer);
|
|
3909
4335
|
};
|
|
3910
4336
|
}
|
|
4337
|
+
// Private methods
|
|
4338
|
+
_createClientProxy() {
|
|
4339
|
+
const methods = /* @__PURE__ */ new Map();
|
|
4340
|
+
const proxy = new Proxy({}, {
|
|
4341
|
+
get: (_target, prop) => typeof prop === "string" ? methods.get(prop) : void 0
|
|
4342
|
+
});
|
|
4343
|
+
return { proxy, methods, overloads: /* @__PURE__ */ new Map() };
|
|
4344
|
+
}
|
|
4345
|
+
_extendClientProxy(peer, entry, def) {
|
|
4346
|
+
for (const methodDef of def.methods.values()) {
|
|
4347
|
+
const existing = entry.overloads.get(methodDef.name)?.get(methodDef.argCount);
|
|
4348
|
+
if (existing !== void 0 && !isSameWireMethod(existing.def, methodDef))
|
|
4349
|
+
throw new Error(
|
|
4350
|
+
`RpcHub.addClient: conflicting definition of '${wireMethodName(methodDef)}' under service '${def.name}' \u2014 a different signature is already registered.`
|
|
4351
|
+
);
|
|
4352
|
+
}
|
|
4353
|
+
const changed = /* @__PURE__ */ new Set();
|
|
4354
|
+
for (const methodDef of def.methods.values()) {
|
|
4355
|
+
let byArgCount = entry.overloads.get(methodDef.name);
|
|
4356
|
+
if (byArgCount === void 0) {
|
|
4357
|
+
byArgCount = /* @__PURE__ */ new Map();
|
|
4358
|
+
entry.overloads.set(methodDef.name, byArgCount);
|
|
4359
|
+
}
|
|
4360
|
+
if (byArgCount.has(methodDef.argCount))
|
|
4361
|
+
continue;
|
|
4362
|
+
byArgCount.set(methodDef.argCount, {
|
|
4363
|
+
def: methodDef,
|
|
4364
|
+
fn: this._createClientMethod(peer, methodDef)
|
|
4365
|
+
});
|
|
4366
|
+
changed.add(methodDef.name);
|
|
4367
|
+
}
|
|
4368
|
+
for (const name of changed) {
|
|
4369
|
+
const byArgCount = entry.overloads.get(name);
|
|
4370
|
+
entry.methods.set(name, buildOverloadFn(name, byArgCount));
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
3911
4373
|
};
|
|
4374
|
+
function buildOverloadFn(name, byArgCount) {
|
|
4375
|
+
if (byArgCount.size === 1) {
|
|
4376
|
+
const [[, only]] = byArgCount;
|
|
4377
|
+
return only.fn;
|
|
4378
|
+
}
|
|
4379
|
+
return (...args) => {
|
|
4380
|
+
const overload = byArgCount.get(args.length);
|
|
4381
|
+
if (overload === void 0)
|
|
4382
|
+
throw new Error(`No overload of ${name} accepts ${args.length} arguments`);
|
|
4383
|
+
return overload.fn(...args);
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
function isSameWireMethod(a, b) {
|
|
4387
|
+
return a.wireArgCount === b.wireArgCount && a.callTypeId === b.callTypeId && a.stream === b.stream && a.noWait === b.noWait && a.remoteExecutionMode === b.remoteExecutionMode;
|
|
4388
|
+
}
|
|
3912
4389
|
|
|
3913
4390
|
// src/rpc-peer-ref-builder.ts
|
|
3914
4391
|
var RpcPeerRefBuilder = class {
|
|
@@ -3975,7 +4452,7 @@ function createMethod(peer, methodDef) {
|
|
|
3975
4452
|
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
3976
4453
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
3977
4454
|
const wireName = wireMethodName(methodDef);
|
|
3978
|
-
const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
4455
|
+
const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== void 0 ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts } : void 0;
|
|
3979
4456
|
const outboundCall = peer.call(wireName, callArgs, options);
|
|
3980
4457
|
return outboundCall.result.promise;
|
|
3981
4458
|
};
|
|
@@ -4015,14 +4492,14 @@ function getStateDescription(state, useLastError = false) {
|
|
|
4015
4492
|
}
|
|
4016
4493
|
|
|
4017
4494
|
// src/rpc-peer-state-monitor.ts
|
|
4018
|
-
var
|
|
4495
|
+
var import_core10 = require("@actuallab/core");
|
|
4019
4496
|
var { infoLog: infoLog2 } = getLogs("RpcPeerStateMonitor");
|
|
4020
4497
|
var JustConnectedPeriodMs = 1500;
|
|
4021
4498
|
var JustDisconnectedPeriodMs = 3e3;
|
|
4022
4499
|
var MinReconnectsInMs = 1e3;
|
|
4023
4500
|
var RpcPeerStateMonitor = class {
|
|
4024
4501
|
peer;
|
|
4025
|
-
stateChanged = new
|
|
4502
|
+
stateChanged = new import_core10.EventHandlerSet();
|
|
4026
4503
|
_rawConnected;
|
|
4027
4504
|
_connectedAt = 0;
|
|
4028
4505
|
_disconnectedAt = 0;
|
|
@@ -4171,8 +4648,10 @@ var RpcPeerStateMonitor = class {
|
|
|
4171
4648
|
ENVELOPE_DELIMITER,
|
|
4172
4649
|
FRAME_DELIMITER,
|
|
4173
4650
|
IncreasingSeqCompressor,
|
|
4651
|
+
REMOTE_EXCEPTION_TYPE_REF,
|
|
4174
4652
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
4175
4653
|
RpcCallStage,
|
|
4654
|
+
RpcCallTimeouts,
|
|
4176
4655
|
RpcCallTypeId,
|
|
4177
4656
|
RpcClientPeer,
|
|
4178
4657
|
RpcClientPeerReconnectDelayer,
|
|
@@ -4227,6 +4706,7 @@ var RpcPeerStateMonitor = class {
|
|
|
4227
4706
|
isConnected,
|
|
4228
4707
|
likelyConnected,
|
|
4229
4708
|
parseStreamRef,
|
|
4709
|
+
readPolymorphismMarker,
|
|
4230
4710
|
resolveStreamRefs,
|
|
4231
4711
|
rpcMethod,
|
|
4232
4712
|
rpcService,
|
|
@@ -4238,6 +4718,7 @@ var RpcPeerStateMonitor = class {
|
|
|
4238
4718
|
splitBinaryFrame,
|
|
4239
4719
|
splitCompactBinaryFrame,
|
|
4240
4720
|
splitFrame,
|
|
4721
|
+
toExceptionInfo,
|
|
4241
4722
|
wireMethodName,
|
|
4242
4723
|
xxh3_64,
|
|
4243
4724
|
xxh3_64str
|