@actuallab/rpc 12.3.14 → 12.3.31
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 +890 -205
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +211 -27
- package/dist/index.d.ts +211 -27
- package/dist/index.js +857 -175
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,9 @@ __export(index_exports, {
|
|
|
33
33
|
ARG_DELIMITER: () => ARG_DELIMITER,
|
|
34
34
|
ENVELOPE_DELIMITER: () => ENVELOPE_DELIMITER,
|
|
35
35
|
FRAME_DELIMITER: () => FRAME_DELIMITER,
|
|
36
|
+
IncreasingSeqCompressor: () => IncreasingSeqCompressor,
|
|
36
37
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT: () => RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
38
|
+
RpcCallStage: () => RpcCallStage,
|
|
37
39
|
RpcCallTypeId: () => RpcCallTypeId,
|
|
38
40
|
RpcClientPeer: () => RpcClientPeer,
|
|
39
41
|
RpcClientPeerReconnectDelayer: () => RpcClientPeerReconnectDelayer,
|
|
@@ -78,6 +80,7 @@ __export(index_exports, {
|
|
|
78
80
|
deserializeBinaryMessage: () => deserializeBinaryMessage,
|
|
79
81
|
deserializeCompactBinaryMessage: () => deserializeCompactBinaryMessage,
|
|
80
82
|
deserializeMessage: () => deserializeMessage,
|
|
83
|
+
getLogs: () => getLogs,
|
|
81
84
|
getMethodsMeta: () => getMethodsMeta,
|
|
82
85
|
getServiceMeta: () => getServiceMeta,
|
|
83
86
|
getStateDescription: () => getStateDescription,
|
|
@@ -101,6 +104,33 @@ __export(index_exports, {
|
|
|
101
104
|
});
|
|
102
105
|
module.exports = __toCommonJS(index_exports);
|
|
103
106
|
|
|
107
|
+
// src/logging.ts
|
|
108
|
+
var import_core = require("@actuallab/core");
|
|
109
|
+
var scopeDefaults = {
|
|
110
|
+
RpcCallTracker: import_core.LogLevel.Warn,
|
|
111
|
+
RpcClient: import_core.LogLevel.Warn,
|
|
112
|
+
RpcClientPeerReconnectDelayer: import_core.LogLevel.Warn,
|
|
113
|
+
RpcConnection: import_core.LogLevel.Warn,
|
|
114
|
+
RpcHub: import_core.LogLevel.Warn,
|
|
115
|
+
RpcInterceptor: import_core.LogLevel.Warn,
|
|
116
|
+
RpcMessageChannelConnection: import_core.LogLevel.Warn,
|
|
117
|
+
RpcMethodRegistry: import_core.LogLevel.Warn,
|
|
118
|
+
RpcOutboundCall: import_core.LogLevel.Warn,
|
|
119
|
+
RpcPeer: import_core.LogLevel.Info,
|
|
120
|
+
RpcPeerStateMonitor: import_core.LogLevel.Warn,
|
|
121
|
+
RpcRemoteObjectTracker: import_core.LogLevel.Warn,
|
|
122
|
+
RpcSerialization: import_core.LogLevel.Warn,
|
|
123
|
+
RpcServiceHost: import_core.LogLevel.Warn,
|
|
124
|
+
RpcSharedObjectTracker: import_core.LogLevel.Warn,
|
|
125
|
+
RpcSharedStream: import_core.LogLevel.Warn,
|
|
126
|
+
RpcStream: import_core.LogLevel.Warn,
|
|
127
|
+
RpcStreamSender: import_core.LogLevel.Warn,
|
|
128
|
+
RpcSystemCallHandler: import_core.LogLevel.Warn,
|
|
129
|
+
RpcSystemCallSender: import_core.LogLevel.Warn,
|
|
130
|
+
RpcWebSocketClient: import_core.LogLevel.Warn
|
|
131
|
+
};
|
|
132
|
+
var getLogs = (0, import_core.createLogProvider)("rpc.", scopeDefaults);
|
|
133
|
+
|
|
104
134
|
// src/rpc-message.ts
|
|
105
135
|
var RpcCallTypeId = /* @__PURE__ */ ((RpcCallTypeId2) => {
|
|
106
136
|
RpcCallTypeId2[RpcCallTypeId2["Regular"] = 0] = "Regular";
|
|
@@ -125,7 +155,44 @@ var ARG_DELIMITER = "";
|
|
|
125
155
|
var FRAME_DELIMITER = "\n";
|
|
126
156
|
|
|
127
157
|
// src/rpc-serialization.ts
|
|
158
|
+
var import_msgpack2 = require("@msgpack/msgpack");
|
|
159
|
+
|
|
160
|
+
// src/msgpack-map-patch.ts
|
|
128
161
|
var import_msgpack = require("@msgpack/msgpack");
|
|
162
|
+
function patchMsgpackEncoderForMaps() {
|
|
163
|
+
const proto = import_msgpack.Encoder.prototype;
|
|
164
|
+
if (proto._hasMapPatch) return;
|
|
165
|
+
proto._hasMapPatch = true;
|
|
166
|
+
const originalDoEncode = proto.doEncode;
|
|
167
|
+
proto.doEncode = function(object, depth) {
|
|
168
|
+
if (object instanceof Map) {
|
|
169
|
+
_encodeMapInstance.call(this, object, depth);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
originalDoEncode.call(this, object, depth);
|
|
173
|
+
};
|
|
174
|
+
function _encodeMapInstance(m, depth) {
|
|
175
|
+
const self = this;
|
|
176
|
+
const size = m.size;
|
|
177
|
+
if (size < 16) {
|
|
178
|
+
self.writeU8(128 + size);
|
|
179
|
+
} else if (size < 65536) {
|
|
180
|
+
self.writeU8(222);
|
|
181
|
+
self.writeU16(size);
|
|
182
|
+
} else {
|
|
183
|
+
self.writeU8(223);
|
|
184
|
+
self.writeU32(size);
|
|
185
|
+
}
|
|
186
|
+
for (const [key, value] of m) {
|
|
187
|
+
self.doEncode(key, depth + 1);
|
|
188
|
+
self.doEncode(value, depth + 1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
patchMsgpackEncoderForMaps();
|
|
193
|
+
|
|
194
|
+
// src/rpc-serialization.ts
|
|
195
|
+
var { warnLog } = getLogs("RpcSerialization");
|
|
129
196
|
function serializeMessage(message, args) {
|
|
130
197
|
const envelope = JSON.stringify(message);
|
|
131
198
|
if (args === void 0 || args.length === 0) {
|
|
@@ -155,9 +222,7 @@ function deserializeMessage(raw) {
|
|
|
155
222
|
try {
|
|
156
223
|
return JSON.parse(s);
|
|
157
224
|
} catch {
|
|
158
|
-
|
|
159
|
-
`[RpcSerialization] failed to parse arg as JSON: ${s.substring(0, 100)}`
|
|
160
|
-
);
|
|
225
|
+
warnLog?.log(`failed to parse arg as JSON: ${s.substring(0, 100)}`);
|
|
161
226
|
return s;
|
|
162
227
|
}
|
|
163
228
|
});
|
|
@@ -166,15 +231,15 @@ function deserializeMessage(raw) {
|
|
|
166
231
|
var textEncoder = new TextEncoder();
|
|
167
232
|
var textDecoder = new TextDecoder();
|
|
168
233
|
var INITIAL_ENCODER_BUFFER_SIZE = 32 * 1024;
|
|
169
|
-
var defaultBinaryEncoder = new
|
|
234
|
+
var defaultBinaryEncoder = new import_msgpack2.Encoder(
|
|
170
235
|
void 0,
|
|
171
236
|
void 0,
|
|
172
237
|
void 0,
|
|
173
238
|
INITIAL_ENCODER_BUFFER_SIZE
|
|
174
239
|
);
|
|
175
|
-
var defaultBinaryDecoder = new
|
|
240
|
+
var defaultBinaryDecoder = new import_msgpack2.Decoder();
|
|
176
241
|
function createBinaryEncoder() {
|
|
177
|
-
return new
|
|
242
|
+
return new import_msgpack2.Encoder(
|
|
178
243
|
void 0,
|
|
179
244
|
void 0,
|
|
180
245
|
void 0,
|
|
@@ -411,8 +476,9 @@ function splitCompactBinaryFrame(frame, registry, decoder = defaultBinaryDecoder
|
|
|
411
476
|
}
|
|
412
477
|
|
|
413
478
|
// src/rpc-connection.ts
|
|
414
|
-
var
|
|
415
|
-
var
|
|
479
|
+
var import_msgpack3 = require("@msgpack/msgpack");
|
|
480
|
+
var import_core2 = require("@actuallab/core");
|
|
481
|
+
var { warnLog: warnLog2 } = getLogs("RpcConnection");
|
|
416
482
|
var WebSocketState = {
|
|
417
483
|
CONNECTING: 0,
|
|
418
484
|
OPEN: 1,
|
|
@@ -422,7 +488,7 @@ var WebSocketState = {
|
|
|
422
488
|
var RpcWebSocketConnection = class {
|
|
423
489
|
_ws;
|
|
424
490
|
_sendBuffer = [];
|
|
425
|
-
_connected = new
|
|
491
|
+
_connected = new import_core2.PromiseSource();
|
|
426
492
|
/** Per-connection msgpack encoder — reused across outbound messages to
|
|
427
493
|
* amortise construction cost and to keep the internal write buffer at
|
|
428
494
|
* its largest observed size (avoids repeated `resizeBuffer` growth on
|
|
@@ -430,7 +496,7 @@ var RpcWebSocketConnection = class {
|
|
|
430
496
|
_encoder = createBinaryEncoder();
|
|
431
497
|
/** Per-connection msgpack decoder — reused across inbound messages
|
|
432
498
|
* rather than reconstructing one for every `decodeMulti` call. */
|
|
433
|
-
_decoder = new
|
|
499
|
+
_decoder = new import_msgpack3.Decoder();
|
|
434
500
|
/** Exposed for callers who need to encode outbound binary messages
|
|
435
501
|
* through this connection (e.g. `serializeBinaryMessage`). Safe to
|
|
436
502
|
* pass to synchronous encode calls on the same event-loop turn. */
|
|
@@ -443,9 +509,9 @@ var RpcWebSocketConnection = class {
|
|
|
443
509
|
_format;
|
|
444
510
|
_methodRegistry;
|
|
445
511
|
binaryMode;
|
|
446
|
-
messageReceived = new
|
|
447
|
-
closed = new
|
|
448
|
-
error = new
|
|
512
|
+
messageReceived = new import_core2.EventHandlerSet();
|
|
513
|
+
closed = new import_core2.EventHandlerSet();
|
|
514
|
+
error = new import_core2.EventHandlerSet();
|
|
449
515
|
constructor(ws, binaryMode = false, format, methodRegistry) {
|
|
450
516
|
this._ws = ws;
|
|
451
517
|
this.binaryMode = binaryMode;
|
|
@@ -474,6 +540,7 @@ var RpcWebSocketConnection = class {
|
|
|
474
540
|
});
|
|
475
541
|
}
|
|
476
542
|
} catch (e) {
|
|
543
|
+
warnLog2?.log("Failed to split binary frame:", e);
|
|
477
544
|
this.error.trigger(e);
|
|
478
545
|
}
|
|
479
546
|
} else if (typeof Blob !== "undefined" && ev.data instanceof Blob && this.binaryMode) {
|
|
@@ -489,6 +556,7 @@ var RpcWebSocketConnection = class {
|
|
|
489
556
|
});
|
|
490
557
|
}
|
|
491
558
|
} catch (e) {
|
|
559
|
+
warnLog2?.log("Failed to split binary blob frame:", e);
|
|
492
560
|
this.error.trigger(e);
|
|
493
561
|
}
|
|
494
562
|
});
|
|
@@ -564,13 +632,13 @@ var RpcWebSocketConnection = class {
|
|
|
564
632
|
};
|
|
565
633
|
|
|
566
634
|
// src/rpc-message-channel-connection.ts
|
|
567
|
-
var
|
|
635
|
+
var import_core3 = require("@actuallab/core");
|
|
568
636
|
var RpcMessageChannelConnection = class {
|
|
569
637
|
_port;
|
|
570
638
|
_open = true;
|
|
571
639
|
binaryMode = false;
|
|
572
|
-
messageReceived = new
|
|
573
|
-
closed = new
|
|
640
|
+
messageReceived = new import_core3.EventHandlerSet();
|
|
641
|
+
closed = new import_core3.EventHandlerSet();
|
|
574
642
|
whenConnected = Promise.resolve();
|
|
575
643
|
// immediately connected
|
|
576
644
|
constructor(port) {
|
|
@@ -659,11 +727,26 @@ function defineRpcService(name, methods) {
|
|
|
659
727
|
}
|
|
660
728
|
|
|
661
729
|
// src/rpc-call-tracker.ts
|
|
662
|
-
var
|
|
730
|
+
var import_core4 = require("@actuallab/core");
|
|
731
|
+
|
|
732
|
+
// src/rpc-call-stage.ts
|
|
733
|
+
var RpcCallStage = {
|
|
734
|
+
/** The call's result has been received from the peer. */
|
|
735
|
+
ResultReady: 1,
|
|
736
|
+
/** The compute call's result has been invalidated (compute-call specific). */
|
|
737
|
+
Invalidated: 3,
|
|
738
|
+
/**
|
|
739
|
+
* Bit flag added on top of ResultReady/Invalidated when the outbound
|
|
740
|
+
* call has been removed from the tracker (unregistered).
|
|
741
|
+
*/
|
|
742
|
+
Unregistered: 4096
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
// src/rpc-call-tracker.ts
|
|
663
746
|
var RpcOutboundCall = class {
|
|
664
747
|
callId;
|
|
665
748
|
method;
|
|
666
|
-
result = new
|
|
749
|
+
result = new import_core4.PromiseSource();
|
|
667
750
|
/** Bitfield of RpcRemoteExecutionMode flags controlling reconnect/resend behavior. */
|
|
668
751
|
remoteExecutionMode;
|
|
669
752
|
/** Whether to remove this call from the tracker on $sys.Ok. Default: true.
|
|
@@ -671,11 +754,35 @@ var RpcOutboundCall = class {
|
|
|
671
754
|
removeOnOk = true;
|
|
672
755
|
/** The serialized wire data — stored for re-sending on reconnect. */
|
|
673
756
|
serializedWireData = "";
|
|
757
|
+
/**
|
|
758
|
+
* Bitfield of {@link RpcCallStage} flags indicating how far this call
|
|
759
|
+
* has progressed. Used by the `$sys.Reconnect` protocol to tell the
|
|
760
|
+
* remote peer which calls are still pending its attention.
|
|
761
|
+
*
|
|
762
|
+
* - 0: brand new, no result yet (the peer is expected to be actively processing).
|
|
763
|
+
* - `ResultReady` (1): `$sys.Ok` received for this call.
|
|
764
|
+
* - `Invalidated` (3): result was invalidated (compute calls only).
|
|
765
|
+
* - `Unregistered` (0x1000): removed from the tracker.
|
|
766
|
+
*/
|
|
767
|
+
completedStage = 0;
|
|
674
768
|
constructor(callId, method, remoteExecutionMode = 7) {
|
|
675
769
|
this.callId = callId;
|
|
676
770
|
this.method = method;
|
|
677
771
|
this.remoteExecutionMode = remoteExecutionMode;
|
|
678
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Returns the stage to report to the remote peer via `$sys.Reconnect`,
|
|
775
|
+
* or `null` if this call should not be reconciled and must be aborted.
|
|
776
|
+
*
|
|
777
|
+
* Mirrors .NET `RpcOutboundCall.GetReconnectStage` at
|
|
778
|
+
* src/ActualLab.Rpc/Infrastructure/RpcOutboundCall.cs.
|
|
779
|
+
*/
|
|
780
|
+
getReconnectStage(isPeerChanged) {
|
|
781
|
+
const mode = this.remoteExecutionMode;
|
|
782
|
+
if (!(mode & RpcRemoteExecutionMode.AllowReconnect)) return null;
|
|
783
|
+
if (isPeerChanged && !(mode & RpcRemoteExecutionMode.AllowResend)) return null;
|
|
784
|
+
return this.completedStage;
|
|
785
|
+
}
|
|
679
786
|
/** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */
|
|
680
787
|
onDisconnect() {
|
|
681
788
|
}
|
|
@@ -697,7 +804,10 @@ var RpcOutboundCallTracker = class {
|
|
|
697
804
|
}
|
|
698
805
|
remove(callId) {
|
|
699
806
|
const call = this._calls.get(callId);
|
|
700
|
-
if (call !== void 0)
|
|
807
|
+
if (call !== void 0) {
|
|
808
|
+
this._calls.delete(callId);
|
|
809
|
+
call.completedStage |= RpcCallStage.Unregistered;
|
|
810
|
+
}
|
|
701
811
|
return call;
|
|
702
812
|
}
|
|
703
813
|
values() {
|
|
@@ -760,15 +870,107 @@ var RpcInboundCallTracker = class {
|
|
|
760
870
|
}
|
|
761
871
|
};
|
|
762
872
|
|
|
873
|
+
// src/increasing-seq-compressor.ts
|
|
874
|
+
var IncreasingSeqCompressor = {
|
|
875
|
+
/**
|
|
876
|
+
* Serialize a non-decreasing sequence of non-negative integers as
|
|
877
|
+
* LEB128-encoded deltas.
|
|
878
|
+
*
|
|
879
|
+
* Throws `RangeError` if the input is not monotonically non-decreasing,
|
|
880
|
+
* or if a value exceeds `Number.MAX_SAFE_INTEGER`.
|
|
881
|
+
*/
|
|
882
|
+
serialize(values) {
|
|
883
|
+
const deltas = [];
|
|
884
|
+
let last = 0;
|
|
885
|
+
let totalBytes = 0;
|
|
886
|
+
for (const value of values) {
|
|
887
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
888
|
+
throw new RangeError(
|
|
889
|
+
`IncreasingSeqCompressor: value must be a non-negative safe integer (got ${value}).`
|
|
890
|
+
);
|
|
891
|
+
const delta = value - last;
|
|
892
|
+
if (delta < 0)
|
|
893
|
+
throw new RangeError(
|
|
894
|
+
`IncreasingSeqCompressor: sequence must be non-decreasing (saw ${value} after ${last}).`
|
|
895
|
+
);
|
|
896
|
+
deltas.push(delta);
|
|
897
|
+
last = value;
|
|
898
|
+
totalBytes += _varUintByteLength(delta);
|
|
899
|
+
}
|
|
900
|
+
const out = new Uint8Array(totalBytes);
|
|
901
|
+
let offset = 0;
|
|
902
|
+
for (const delta of deltas) {
|
|
903
|
+
offset = _writeVarUint(out, delta, offset);
|
|
904
|
+
}
|
|
905
|
+
return out;
|
|
906
|
+
},
|
|
907
|
+
/**
|
|
908
|
+
* Decode bytes produced by {@link serialize} back into the original
|
|
909
|
+
* sorted sequence of non-negative integers.
|
|
910
|
+
*/
|
|
911
|
+
deserialize(data) {
|
|
912
|
+
const result = [];
|
|
913
|
+
let last = 0;
|
|
914
|
+
let offset = 0;
|
|
915
|
+
while (offset < data.length) {
|
|
916
|
+
const [delta, next] = _readVarUint(data, offset);
|
|
917
|
+
offset = next;
|
|
918
|
+
last += delta;
|
|
919
|
+
result.push(last);
|
|
920
|
+
}
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
var LOW_BITS = 127;
|
|
925
|
+
var HIGH_BIT = 128;
|
|
926
|
+
function _varUintByteLength(value) {
|
|
927
|
+
let n = 0;
|
|
928
|
+
let v = value;
|
|
929
|
+
while (v >= HIGH_BIT) {
|
|
930
|
+
n++;
|
|
931
|
+
v = Math.floor(v / 128);
|
|
932
|
+
}
|
|
933
|
+
return n + 1;
|
|
934
|
+
}
|
|
935
|
+
function _writeVarUint(out, value, offset) {
|
|
936
|
+
let v = value;
|
|
937
|
+
while (v >= HIGH_BIT) {
|
|
938
|
+
out[offset++] = (HIGH_BIT | v & LOW_BITS) & 255;
|
|
939
|
+
v = Math.floor(v / 128);
|
|
940
|
+
}
|
|
941
|
+
out[offset++] = v & 255;
|
|
942
|
+
return offset;
|
|
943
|
+
}
|
|
944
|
+
function _readVarUint(data, offset) {
|
|
945
|
+
let value = 0;
|
|
946
|
+
let shift = 0;
|
|
947
|
+
while (shift < 63) {
|
|
948
|
+
if (offset >= data.length)
|
|
949
|
+
throw new RangeError("IncreasingSeqCompressor: truncated VarUInt.");
|
|
950
|
+
const b2 = data[offset++];
|
|
951
|
+
value += (b2 & LOW_BITS) * Math.pow(2, shift);
|
|
952
|
+
if (b2 <= LOW_BITS) return [value, offset];
|
|
953
|
+
shift += 7;
|
|
954
|
+
}
|
|
955
|
+
if (offset >= data.length)
|
|
956
|
+
throw new RangeError("IncreasingSeqCompressor: truncated VarUInt.");
|
|
957
|
+
const b = data[offset++];
|
|
958
|
+
if (b > 1)
|
|
959
|
+
throw new RangeError("IncreasingSeqCompressor: VarUInt value exceeds 64 bits.");
|
|
960
|
+
value += b * Math.pow(2, 63);
|
|
961
|
+
return [value, offset];
|
|
962
|
+
}
|
|
963
|
+
|
|
763
964
|
// src/rpc-stream.ts
|
|
764
965
|
var import_denque = __toESM(require("denque"), 1);
|
|
765
|
-
var
|
|
966
|
+
var import_core6 = require("@actuallab/core");
|
|
766
967
|
|
|
767
968
|
// src/rpc-object.ts
|
|
768
969
|
var RpcObjectKind = { Local: 0, Remote: 1 };
|
|
769
970
|
|
|
770
971
|
// src/rpc-stream-sender.ts
|
|
771
|
-
var
|
|
972
|
+
var import_core5 = require("@actuallab/core");
|
|
973
|
+
var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
|
|
772
974
|
var DEFAULT_ACK_PERIOD = 256;
|
|
773
975
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
774
976
|
var RpcStreamSender = class _RpcStreamSender {
|
|
@@ -781,15 +983,29 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
781
983
|
peer;
|
|
782
984
|
ackPeriod;
|
|
783
985
|
ackAdvance;
|
|
986
|
+
/**
|
|
987
|
+
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
988
|
+
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
989
|
+
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
990
|
+
* before force-closing via `iterator.return()`.
|
|
991
|
+
*
|
|
992
|
+
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
993
|
+
* so `disconnect()` force-closes immediately.
|
|
994
|
+
*/
|
|
995
|
+
sourceUsesAbortSignal;
|
|
996
|
+
// -- Pump state --
|
|
997
|
+
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
784
998
|
_nextIndex = 0;
|
|
785
999
|
_ended = false;
|
|
786
|
-
_started = new
|
|
1000
|
+
_started = new import_core5.PromiseSource();
|
|
1001
|
+
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
787
1002
|
_lastAckedIndex = 0;
|
|
788
|
-
_ackWaiting = null;
|
|
789
|
-
_resetRequested = false;
|
|
790
1003
|
_abortController = new AbortController();
|
|
791
1004
|
_iterator = null;
|
|
792
|
-
|
|
1005
|
+
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
1006
|
+
_acks = [];
|
|
1007
|
+
_whenAckReady = null;
|
|
1008
|
+
constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true, sourceUsesAbortSignal = false) {
|
|
793
1009
|
const localId = peer.sharedObjects.nextId();
|
|
794
1010
|
this.id = { hostId: peer.hub.hubId, localId };
|
|
795
1011
|
this.allowReconnect = allowReconnect;
|
|
@@ -798,6 +1014,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
798
1014
|
this.peer = peer;
|
|
799
1015
|
this.ackPeriod = ackPeriod;
|
|
800
1016
|
this.ackAdvance = ackAdvance;
|
|
1017
|
+
this.sourceUsesAbortSignal = sourceUsesAbortSignal;
|
|
801
1018
|
}
|
|
802
1019
|
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
803
1020
|
get abortSignal() {
|
|
@@ -807,56 +1024,61 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
807
1024
|
toRef() {
|
|
808
1025
|
return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
|
|
809
1026
|
}
|
|
810
|
-
/**
|
|
1027
|
+
/**
|
|
1028
|
+
* Called by system call handler when $sys.Ack is received from the client.
|
|
1029
|
+
*
|
|
1030
|
+
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
1031
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
1032
|
+
*/
|
|
811
1033
|
onAck(nextIndex, hostId) {
|
|
812
1034
|
const mustReset = hostId !== "" && hostId !== _RpcStreamSender._emptyGuid;
|
|
813
|
-
if (!this._started.isCompleted)
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
this.
|
|
818
|
-
this._lastAckedIndex = nextIndex;
|
|
819
|
-
this._resetRequested = true;
|
|
820
|
-
} else {
|
|
821
|
-
if (mustReset || nextIndex > this._lastAckedIndex) {
|
|
822
|
-
this._lastAckedIndex = nextIndex;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
if (this._ackWaiting) {
|
|
826
|
-
this._ackWaiting.resolve();
|
|
827
|
-
this._ackWaiting = null;
|
|
1035
|
+
if (!this._started.isCompleted) this._started.resolve();
|
|
1036
|
+
this._acks.push({ nextIndex, mustReset });
|
|
1037
|
+
if (this._whenAckReady) {
|
|
1038
|
+
this._whenAckReady.resolve();
|
|
1039
|
+
this._whenAckReady = null;
|
|
828
1040
|
}
|
|
829
1041
|
}
|
|
830
1042
|
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
831
1043
|
onAckEnd(_hostId) {
|
|
832
1044
|
this.disconnect();
|
|
833
1045
|
}
|
|
834
|
-
/**
|
|
1046
|
+
/**
|
|
1047
|
+
* Send a single item to the client at the current `_nextIndex`.
|
|
1048
|
+
*
|
|
1049
|
+
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1050
|
+
* the item is considered "sent" from the pump's perspective so the replay
|
|
1051
|
+
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
1052
|
+
* message is dropped; the item will be resent from the replay buffer
|
|
1053
|
+
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1054
|
+
*/
|
|
835
1055
|
sendItem(item) {
|
|
836
1056
|
if (this._ended) return;
|
|
837
1057
|
const conn = this.peer.connection;
|
|
838
|
-
if (
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1058
|
+
if (conn) {
|
|
1059
|
+
this.peer.hub.systemCallSender.item(
|
|
1060
|
+
conn,
|
|
1061
|
+
this.peer.format,
|
|
1062
|
+
this.id.localId,
|
|
1063
|
+
this._nextIndex,
|
|
1064
|
+
item
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
846
1067
|
this._nextIndex++;
|
|
847
1068
|
}
|
|
848
|
-
/** Send a batch of items to the client. */
|
|
1069
|
+
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
849
1070
|
sendBatch(items) {
|
|
850
1071
|
if (this._ended || items.length === 0) return;
|
|
851
1072
|
const conn = this.peer.connection;
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1073
|
+
if (conn) {
|
|
1074
|
+
this.peer.hub.systemCallSender.batch(
|
|
1075
|
+
conn,
|
|
1076
|
+
this.peer.format,
|
|
1077
|
+
this.id.localId,
|
|
1078
|
+
this._nextIndex,
|
|
1079
|
+
items
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
860
1082
|
this._nextIndex += items.length;
|
|
861
1083
|
}
|
|
862
1084
|
/** Signal stream completion to the client. */
|
|
@@ -876,76 +1098,274 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
876
1098
|
}
|
|
877
1099
|
/**
|
|
878
1100
|
* Consume an AsyncIterable and send all items to the client.
|
|
879
|
-
* Waits for the client's initial Ack before starting to pump items.
|
|
880
1101
|
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
* one where `canSkipTo` returns true.
|
|
1102
|
+
* Dispatches to the non-real-time or real-time pump, matching .NET
|
|
1103
|
+
* `RpcSharedStream<T>.OnRun` at
|
|
1104
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:105-108.
|
|
885
1105
|
*/
|
|
886
1106
|
async writeFrom(source) {
|
|
887
1107
|
await this._started.promise;
|
|
1108
|
+
if (this._ended) return;
|
|
888
1109
|
const iterator = source[Symbol.asyncIterator]();
|
|
889
1110
|
this._iterator = iterator;
|
|
1111
|
+
const state = { iteratorDone: false };
|
|
890
1112
|
try {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1113
|
+
if (this.isRealTime) {
|
|
1114
|
+
await this._runRealTime(iterator, state);
|
|
1115
|
+
} else {
|
|
1116
|
+
await this._runNonRealTime(iterator, state);
|
|
1117
|
+
}
|
|
1118
|
+
} finally {
|
|
1119
|
+
if (state.iteratorDone && this._iterator === iterator) {
|
|
1120
|
+
this._iterator = null;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Non-real-time pump — back-pressure through buffered ACKs.
|
|
1126
|
+
*
|
|
1127
|
+
* Direct port of .NET `OnRunNonRealTime` at
|
|
1128
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:110-217.
|
|
1129
|
+
*/
|
|
1130
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _ended changes across awaits */
|
|
1131
|
+
async _runNonRealTime(iterator, state) {
|
|
1132
|
+
const buffer = new import_core5.RingBuffer(this.ackAdvance + 1);
|
|
1133
|
+
let bufferStart = 0;
|
|
1134
|
+
let isFullyBuffered = false;
|
|
1135
|
+
while (true) {
|
|
1136
|
+
let ack = this._tryProcessAcks();
|
|
1137
|
+
if (!ack) {
|
|
1138
|
+
await this._waitAckReady();
|
|
1139
|
+
if (this._ended) return;
|
|
1140
|
+
ack = this._tryProcessAcks();
|
|
1141
|
+
if (!ack) {
|
|
1142
|
+
warnLog3?.log("Something is off: couldn't read an acknowledgement");
|
|
1143
|
+
return;
|
|
900
1144
|
}
|
|
901
|
-
|
|
902
|
-
|
|
1145
|
+
}
|
|
1146
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1147
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1148
|
+
if (shift > 0) {
|
|
1149
|
+
buffer.moveHead(shift);
|
|
1150
|
+
bufferStart += shift;
|
|
1151
|
+
}
|
|
1152
|
+
if (this._nextIndex < bufferStart) {
|
|
1153
|
+
if (!this._ended)
|
|
1154
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1158
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1159
|
+
while (this._nextIndex < maxIndex) {
|
|
1160
|
+
if (this._ended) return;
|
|
1161
|
+
if (bufferIndex < buffer.count) {
|
|
1162
|
+
const item = buffer.get(bufferIndex++);
|
|
1163
|
+
if (item.kind === "value") {
|
|
1164
|
+
this.sendItem(item.value);
|
|
1165
|
+
} else if (item.kind === "end") {
|
|
1166
|
+
if (!this._ended) this.sendEnd();
|
|
1167
|
+
return;
|
|
1168
|
+
} else {
|
|
1169
|
+
if (!this._ended) this.sendEnd(item.error);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
903
1172
|
continue;
|
|
904
1173
|
}
|
|
905
|
-
if (
|
|
906
|
-
|
|
1174
|
+
if (isFullyBuffered) break;
|
|
1175
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1176
|
+
let pulled;
|
|
1177
|
+
try {
|
|
1178
|
+
const r = await iterator.next();
|
|
907
1179
|
if (this._ended) return;
|
|
908
|
-
|
|
909
|
-
|
|
1180
|
+
if (r.done) {
|
|
1181
|
+
pulled = _endItem;
|
|
1182
|
+
isFullyBuffered = true;
|
|
1183
|
+
state.iteratorDone = true;
|
|
1184
|
+
} else {
|
|
1185
|
+
pulled = { kind: "value", value: r.value };
|
|
1186
|
+
}
|
|
1187
|
+
} catch (e) {
|
|
1188
|
+
state.iteratorDone = true;
|
|
1189
|
+
isFullyBuffered = true;
|
|
1190
|
+
pulled = {
|
|
1191
|
+
kind: "error",
|
|
1192
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1193
|
+
};
|
|
910
1194
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1195
|
+
buffer.pushTail(pulled);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Real-time pump — drops stale items to stay close to the source's head.
|
|
1201
|
+
*
|
|
1202
|
+
* Direct port of .NET `OnRunRealTime` at
|
|
1203
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:219-420.
|
|
1204
|
+
*/
|
|
1205
|
+
async _runRealTime(iterator, state) {
|
|
1206
|
+
const buffer = new import_core5.RingBuffer(this.ackAdvance + 1);
|
|
1207
|
+
let bufferStart = 0;
|
|
1208
|
+
let isFullyBuffered = false;
|
|
1209
|
+
while (true) {
|
|
1210
|
+
let ack = this._tryProcessAcks();
|
|
1211
|
+
if (!ack) {
|
|
1212
|
+
await this._waitAckReady();
|
|
1213
|
+
if (this._ended) return;
|
|
1214
|
+
ack = this._tryProcessAcks();
|
|
1215
|
+
if (!ack) {
|
|
1216
|
+
warnLog3?.log("Something is off: couldn't read an acknowledgement");
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1221
|
+
if (ack.mustReset && !isFullyBuffered) {
|
|
1222
|
+
buffer.clear();
|
|
1223
|
+
bufferStart = this._nextIndex;
|
|
1224
|
+
while (true) {
|
|
914
1225
|
if (this._ended) return;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1226
|
+
let item;
|
|
1227
|
+
let accepted = false;
|
|
1228
|
+
try {
|
|
1229
|
+
const r = await iterator.next();
|
|
1230
|
+
if (this._ended) return;
|
|
1231
|
+
if (r.done) {
|
|
1232
|
+
item = _endItem;
|
|
1233
|
+
isFullyBuffered = true;
|
|
1234
|
+
state.iteratorDone = true;
|
|
1235
|
+
} else if (this.canSkipTo(r.value)) {
|
|
1236
|
+
item = { kind: "value", value: r.value };
|
|
1237
|
+
accepted = true;
|
|
1238
|
+
} else {
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
state.iteratorDone = true;
|
|
1243
|
+
isFullyBuffered = true;
|
|
1244
|
+
item = {
|
|
1245
|
+
kind: "error",
|
|
1246
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1247
|
+
};
|
|
922
1248
|
}
|
|
1249
|
+
buffer.pushTail(item);
|
|
1250
|
+
if (accepted || isFullyBuffered) break;
|
|
923
1251
|
}
|
|
1252
|
+
}
|
|
1253
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1254
|
+
if (shift > 0) {
|
|
1255
|
+
buffer.moveHead(shift);
|
|
1256
|
+
bufferStart += shift;
|
|
1257
|
+
}
|
|
1258
|
+
if (this._nextIndex < bufferStart) {
|
|
1259
|
+
if (!this._ended)
|
|
1260
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1264
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1265
|
+
while (this._nextIndex < maxIndex) {
|
|
924
1266
|
if (this._ended) return;
|
|
925
|
-
if (
|
|
926
|
-
|
|
1267
|
+
if (this._acks.length > 0) break;
|
|
1268
|
+
if (bufferIndex < buffer.count) {
|
|
1269
|
+
const item = buffer.get(bufferIndex++);
|
|
1270
|
+
if (item.kind === "value") {
|
|
1271
|
+
this.sendItem(item.value);
|
|
1272
|
+
} else if (item.kind === "end") {
|
|
1273
|
+
if (!this._ended) this.sendEnd();
|
|
1274
|
+
return;
|
|
1275
|
+
} else {
|
|
1276
|
+
if (!this._ended) this.sendEnd(item.error);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
continue;
|
|
927
1280
|
}
|
|
928
|
-
if (
|
|
929
|
-
|
|
930
|
-
|
|
1281
|
+
if (isFullyBuffered) break;
|
|
1282
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1283
|
+
let pulled;
|
|
1284
|
+
try {
|
|
1285
|
+
const r = await iterator.next();
|
|
1286
|
+
if (this._ended) return;
|
|
1287
|
+
if (r.done) {
|
|
1288
|
+
pulled = _endItem;
|
|
1289
|
+
isFullyBuffered = true;
|
|
1290
|
+
state.iteratorDone = true;
|
|
1291
|
+
} else {
|
|
1292
|
+
pulled = { kind: "value", value: r.value };
|
|
1293
|
+
}
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
state.iteratorDone = true;
|
|
1296
|
+
isFullyBuffered = true;
|
|
1297
|
+
pulled = {
|
|
1298
|
+
kind: "error",
|
|
1299
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1300
|
+
};
|
|
931
1301
|
}
|
|
1302
|
+
buffer.pushTail(pulled);
|
|
932
1303
|
}
|
|
933
|
-
if (!this._ended) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1304
|
+
if (this._nextIndex >= maxIndex && !isFullyBuffered && !this._ended) {
|
|
1305
|
+
let latestValue;
|
|
1306
|
+
let haveLatest = false;
|
|
1307
|
+
let exitReason = "ack";
|
|
1308
|
+
let exitError = null;
|
|
1309
|
+
drain: while (!this._ended) {
|
|
1310
|
+
if (this._acks.length > 0) {
|
|
1311
|
+
exitReason = "ack";
|
|
1312
|
+
break drain;
|
|
1313
|
+
}
|
|
1314
|
+
let r;
|
|
1315
|
+
try {
|
|
1316
|
+
r = await iterator.next();
|
|
1317
|
+
} catch (e) {
|
|
1318
|
+
state.iteratorDone = true;
|
|
1319
|
+
isFullyBuffered = true;
|
|
1320
|
+
exitReason = "error";
|
|
1321
|
+
exitError = e instanceof Error ? e : new Error(String(e));
|
|
1322
|
+
break drain;
|
|
1323
|
+
}
|
|
1324
|
+
if (this._ended) return;
|
|
1325
|
+
if (r.done) {
|
|
1326
|
+
state.iteratorDone = true;
|
|
1327
|
+
isFullyBuffered = true;
|
|
1328
|
+
exitReason = "end";
|
|
1329
|
+
break drain;
|
|
1330
|
+
}
|
|
1331
|
+
if (this.canSkipTo(r.value)) {
|
|
1332
|
+
latestValue = r.value;
|
|
1333
|
+
haveLatest = true;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (exitReason === "end" || exitReason === "error") {
|
|
1337
|
+
if (haveLatest && !this._ended) this.sendItem(latestValue);
|
|
1338
|
+
if (!this._ended) this.sendEnd(exitError);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (haveLatest) {
|
|
1342
|
+
const wasFull = buffer.isFull;
|
|
1343
|
+
buffer.pushTailAndMoveHeadIfFull({ kind: "value", value: latestValue });
|
|
1344
|
+
if (wasFull) bufferStart++;
|
|
1345
|
+
}
|
|
939
1346
|
}
|
|
940
1347
|
}
|
|
941
1348
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1349
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1350
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1351
|
+
_tryProcessAcks() {
|
|
1352
|
+
if (this._acks.length === 0) return null;
|
|
1353
|
+
let last = null;
|
|
1354
|
+
while (this._acks.length > 0) {
|
|
1355
|
+
const a = this._acks.shift();
|
|
1356
|
+
last = a;
|
|
1357
|
+
if (a.mustReset || this._nextIndex < a.nextIndex) {
|
|
1358
|
+
this._nextIndex = a.nextIndex;
|
|
1359
|
+
}
|
|
948
1360
|
}
|
|
1361
|
+
return last;
|
|
1362
|
+
}
|
|
1363
|
+
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1364
|
+
async _waitAckReady() {
|
|
1365
|
+
if (this._acks.length > 0 || this._ended) return;
|
|
1366
|
+
this._whenAckReady = new import_core5.PromiseSource();
|
|
1367
|
+
await this._whenAckReady.promise;
|
|
1368
|
+
this._whenAckReady = null;
|
|
949
1369
|
}
|
|
950
1370
|
// -- IRpcObject --
|
|
951
1371
|
reconnect() {
|
|
@@ -957,19 +1377,41 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
957
1377
|
if (!this._started.isCompleted) {
|
|
958
1378
|
this._started.resolve();
|
|
959
1379
|
}
|
|
960
|
-
if (this.
|
|
961
|
-
this.
|
|
962
|
-
this.
|
|
1380
|
+
if (this._whenAckReady) {
|
|
1381
|
+
this._whenAckReady.resolve();
|
|
1382
|
+
this._whenAckReady = null;
|
|
963
1383
|
}
|
|
964
|
-
|
|
965
|
-
|
|
1384
|
+
this.peer.sharedObjects.unregister(this);
|
|
1385
|
+
const iterator = this._iterator;
|
|
1386
|
+
if (!iterator) return;
|
|
1387
|
+
if (!this.sourceUsesAbortSignal) {
|
|
966
1388
|
this._iterator = null;
|
|
1389
|
+
this._forceCloseIterator(iterator);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
setTimeout(() => {
|
|
1393
|
+
if (this._iterator === iterator) {
|
|
1394
|
+
this._iterator = null;
|
|
1395
|
+
this._forceCloseIterator(iterator);
|
|
1396
|
+
}
|
|
1397
|
+
}, RpcStream.disconnectGracePeriodMs);
|
|
1398
|
+
}
|
|
1399
|
+
_forceCloseIterator(iterator) {
|
|
1400
|
+
try {
|
|
1401
|
+
const ret = iterator.return?.(void 0);
|
|
1402
|
+
if (ret) void ret.catch(() => {
|
|
1403
|
+
});
|
|
1404
|
+
} catch {
|
|
967
1405
|
}
|
|
968
|
-
this.peer.sharedObjects.unregister(this);
|
|
969
1406
|
}
|
|
970
1407
|
};
|
|
1408
|
+
var _endItem = { kind: "end" };
|
|
1409
|
+
function _clamp(value, min, max) {
|
|
1410
|
+
return value < min ? min : value > max ? max : value;
|
|
1411
|
+
}
|
|
971
1412
|
|
|
972
1413
|
// src/rpc-stream.ts
|
|
1414
|
+
var { warnLog: warnLog4 } = getLogs("RpcStream");
|
|
973
1415
|
var DEFAULT_ACK_PERIOD2 = 30;
|
|
974
1416
|
var DEFAULT_ACK_ADVANCE2 = 61;
|
|
975
1417
|
function parseStreamRef(value) {
|
|
@@ -1003,6 +1445,15 @@ function parseStreamRef(value) {
|
|
|
1003
1445
|
return null;
|
|
1004
1446
|
}
|
|
1005
1447
|
var RpcStream = class _RpcStream {
|
|
1448
|
+
/**
|
|
1449
|
+
* Grace period (ms) given to a factory-form local source to honor its
|
|
1450
|
+
* AbortSignal and exit naturally before the sender force-closes the
|
|
1451
|
+
* iterator via `iterator.return()`.
|
|
1452
|
+
*
|
|
1453
|
+
* Only applies to sources provided as `(abortSignal) => AsyncIterable<T>`.
|
|
1454
|
+
* Plain `AsyncIterable<T>` sources are force-closed immediately on disconnect.
|
|
1455
|
+
*/
|
|
1456
|
+
static disconnectGracePeriodMs = 100;
|
|
1006
1457
|
id;
|
|
1007
1458
|
kind;
|
|
1008
1459
|
allowReconnect;
|
|
@@ -1080,17 +1531,19 @@ var RpcStream = class _RpcStream {
|
|
|
1080
1531
|
throw new Error("toRef() was already called with a different peer.");
|
|
1081
1532
|
return this._toRefValue;
|
|
1082
1533
|
}
|
|
1534
|
+
const isFactory = typeof this.localSource === "function";
|
|
1083
1535
|
const sender = new RpcStreamSender(
|
|
1084
1536
|
peer,
|
|
1085
1537
|
this.ackPeriod,
|
|
1086
1538
|
this.ackAdvance,
|
|
1087
1539
|
this.allowReconnect,
|
|
1088
1540
|
this.isRealTime,
|
|
1089
|
-
this.canSkipTo
|
|
1541
|
+
this.canSkipTo,
|
|
1542
|
+
isFactory
|
|
1090
1543
|
);
|
|
1091
1544
|
peer.sharedObjects.register(sender);
|
|
1092
1545
|
this._sender = sender;
|
|
1093
|
-
const source =
|
|
1546
|
+
const source = isFactory ? this.localSource(sender.abortSignal) : this.localSource;
|
|
1094
1547
|
this._whenSent = sender.writeFrom(source);
|
|
1095
1548
|
this._toRefValue = peer.format.isBinary ? {
|
|
1096
1549
|
SerializedId: [sender.id.hostId, sender.id.localId],
|
|
@@ -1104,9 +1557,7 @@ var RpcStream = class _RpcStream {
|
|
|
1104
1557
|
onItem(index, item) {
|
|
1105
1558
|
if (this._disposed || this._completed) return;
|
|
1106
1559
|
if (index > this._nextExpectedIndex) {
|
|
1107
|
-
|
|
1108
|
-
`[RpcStream] item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`
|
|
1109
|
-
);
|
|
1560
|
+
warnLog4?.log(`item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1110
1561
|
if (!this.allowReconnect) {
|
|
1111
1562
|
this._completed = true;
|
|
1112
1563
|
this._completionError = new Error(
|
|
@@ -1131,9 +1582,7 @@ var RpcStream = class _RpcStream {
|
|
|
1131
1582
|
onBatch(index, items) {
|
|
1132
1583
|
if (this._disposed || this._completed) return;
|
|
1133
1584
|
if (index > this._nextExpectedIndex) {
|
|
1134
|
-
|
|
1135
|
-
`[RpcStream] batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`
|
|
1136
|
-
);
|
|
1585
|
+
warnLog4?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1137
1586
|
if (!this.allowReconnect) {
|
|
1138
1587
|
this._completed = true;
|
|
1139
1588
|
this._completionError = new Error(
|
|
@@ -1204,7 +1653,7 @@ var RpcStream = class _RpcStream {
|
|
|
1204
1653
|
if (this._completionError) throw this._completionError;
|
|
1205
1654
|
return { value: void 0, done: true };
|
|
1206
1655
|
}
|
|
1207
|
-
this._consumerWaiting = new
|
|
1656
|
+
this._consumerWaiting = new import_core6.PromiseSource();
|
|
1208
1657
|
await this._consumerWaiting.promise;
|
|
1209
1658
|
}
|
|
1210
1659
|
},
|
|
@@ -1300,15 +1749,37 @@ function resolveStreamRefs(value, peer) {
|
|
|
1300
1749
|
return value;
|
|
1301
1750
|
}
|
|
1302
1751
|
|
|
1752
|
+
// src/base64.ts
|
|
1753
|
+
function base64Encode(bytes) {
|
|
1754
|
+
const chunkSize = 32768;
|
|
1755
|
+
let binary = "";
|
|
1756
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
1757
|
+
binary += String.fromCharCode.apply(
|
|
1758
|
+
null,
|
|
1759
|
+
bytes.subarray(i, Math.min(bytes.length, i + chunkSize))
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
return btoa(binary);
|
|
1763
|
+
}
|
|
1764
|
+
function base64Decode(encoded) {
|
|
1765
|
+
const binary = atob(encoded);
|
|
1766
|
+
const out = new Uint8Array(binary.length);
|
|
1767
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
1768
|
+
return out;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1303
1771
|
// src/rpc-system-call-handler.ts
|
|
1772
|
+
var { debugLog, warnLog: warnLog5 } = getLogs("RpcSystemCallHandler");
|
|
1304
1773
|
var RpcSystemCallHandler = class {
|
|
1305
1774
|
handle(message, args, peer) {
|
|
1306
1775
|
const method = message.Method;
|
|
1307
1776
|
const relatedId = message.RelatedId ?? 0;
|
|
1777
|
+
debugLog?.log(`'${peer.ref}': handle ${method}#${relatedId}`);
|
|
1308
1778
|
switch (method) {
|
|
1309
1779
|
case RpcSystemCalls.ok: {
|
|
1310
1780
|
const call = peer.outbound.get(relatedId);
|
|
1311
1781
|
if (call !== void 0) {
|
|
1782
|
+
call.completedStage |= RpcCallStage.ResultReady;
|
|
1312
1783
|
if (call.removeOnOk) {
|
|
1313
1784
|
peer.outbound.remove(relatedId);
|
|
1314
1785
|
}
|
|
@@ -1316,11 +1787,21 @@ var RpcSystemCallHandler = class {
|
|
|
1316
1787
|
}
|
|
1317
1788
|
break;
|
|
1318
1789
|
}
|
|
1790
|
+
case RpcSystemCalls.reconnect: {
|
|
1791
|
+
this._handleReconnect(relatedId, args, peer);
|
|
1792
|
+
break;
|
|
1793
|
+
}
|
|
1319
1794
|
case RpcSystemCalls.error: {
|
|
1320
1795
|
const call = peer.outbound.remove(relatedId);
|
|
1321
1796
|
if (call !== void 0) {
|
|
1322
1797
|
const errorInfo = args[0];
|
|
1323
1798
|
const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
|
|
1799
|
+
const errorType = errorInfo?.TypeRef ?? errorInfo?.typeRef;
|
|
1800
|
+
if (errorType !== null && typeof errorType === "object") {
|
|
1801
|
+
const t = errorType;
|
|
1802
|
+
if (t.TypeName === "RpcRerouteException" || t.typeName === "RpcRerouteException")
|
|
1803
|
+
warnLog5?.log("Got RpcRerouteException from remote peer:", msg);
|
|
1804
|
+
}
|
|
1324
1805
|
call.result.reject(new Error(msg));
|
|
1325
1806
|
}
|
|
1326
1807
|
break;
|
|
@@ -1334,25 +1815,20 @@ var RpcSystemCallHandler = class {
|
|
|
1334
1815
|
}
|
|
1335
1816
|
case RpcSystemCalls.item: {
|
|
1336
1817
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1337
|
-
if (!stream)
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
);
|
|
1341
|
-
} else {
|
|
1818
|
+
if (!stream)
|
|
1819
|
+
debugLog?.log(`$sys.I: no stream for relatedId=${relatedId}`);
|
|
1820
|
+
else
|
|
1342
1821
|
stream.onItem(
|
|
1343
1822
|
args[0],
|
|
1344
1823
|
resolveStreamRefs(args[1], peer)
|
|
1345
1824
|
);
|
|
1346
|
-
}
|
|
1347
1825
|
break;
|
|
1348
1826
|
}
|
|
1349
1827
|
case RpcSystemCalls.batch: {
|
|
1350
1828
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1351
|
-
if (!stream)
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
);
|
|
1355
|
-
} else if (Array.isArray(args[1])) {
|
|
1829
|
+
if (!stream)
|
|
1830
|
+
debugLog?.log(`$sys.B: no stream for relatedId=${relatedId}`);
|
|
1831
|
+
else if (Array.isArray(args[1])) {
|
|
1356
1832
|
const items = args[1];
|
|
1357
1833
|
for (let i = 0; i < items.length; i++)
|
|
1358
1834
|
items[i] = resolveStreamRefs(items[i], peer);
|
|
@@ -1363,9 +1839,7 @@ var RpcSystemCallHandler = class {
|
|
|
1363
1839
|
case RpcSystemCalls.end: {
|
|
1364
1840
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1365
1841
|
if (!stream) {
|
|
1366
|
-
|
|
1367
|
-
`[RpcSysHandler] $sys.End: no stream for relatedId=${relatedId}`
|
|
1368
|
-
);
|
|
1842
|
+
debugLog?.log(`$sys.End: no stream for relatedId=${relatedId}`);
|
|
1369
1843
|
} else {
|
|
1370
1844
|
const errorInfo = args[1];
|
|
1371
1845
|
const msg = errorInfo?.Message ?? errorInfo?.message;
|
|
@@ -1404,9 +1878,57 @@ var RpcSystemCallHandler = class {
|
|
|
1404
1878
|
}
|
|
1405
1879
|
}
|
|
1406
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Server-side handler for the `$sys.Reconnect` call. Parses the peer's
|
|
1883
|
+
* report of in-flight call IDs (grouped by stage, IncreasingSeqCompressor-
|
|
1884
|
+
* encoded, base64-wrapped for JSON transport), computes the subset this
|
|
1885
|
+
* peer does not recognize, and replies with a compressed byte[] of the
|
|
1886
|
+
* unknown IDs wrapped in `$sys.Ok`.
|
|
1887
|
+
*
|
|
1888
|
+
* @param relatedId The outbound call ID the client used for this
|
|
1889
|
+
* `$sys.Reconnect` invocation — we send `$sys.Ok` back
|
|
1890
|
+
* with the same `RelatedId`.
|
|
1891
|
+
*/
|
|
1892
|
+
_handleReconnect(relatedId, args, peer) {
|
|
1893
|
+
if (peer.connection === void 0) return;
|
|
1894
|
+
let unknownIds = [];
|
|
1895
|
+
try {
|
|
1896
|
+
const stagesRaw = args[1];
|
|
1897
|
+
if (stagesRaw && typeof stagesRaw === "object" && !Array.isArray(stagesRaw)) {
|
|
1898
|
+
const unknownSet = /* @__PURE__ */ new Set();
|
|
1899
|
+
for (const [, rawValue] of Object.entries(stagesRaw)) {
|
|
1900
|
+
const bytes = _toBytes(rawValue);
|
|
1901
|
+
if (bytes === null) continue;
|
|
1902
|
+
const callIds = IncreasingSeqCompressor.deserialize(bytes);
|
|
1903
|
+
for (const callId of callIds) {
|
|
1904
|
+
if (peer.inbound.get(callId) === void 0) {
|
|
1905
|
+
unknownSet.add(callId);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
unknownIds = [...unknownSet].sort((a, b) => a - b);
|
|
1910
|
+
}
|
|
1911
|
+
} catch {
|
|
1912
|
+
}
|
|
1913
|
+
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1914
|
+
const responseValue = peer.format.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1915
|
+
peer.hub.systemCallSender.ok(peer.connection, peer.format, relatedId, responseValue);
|
|
1916
|
+
}
|
|
1407
1917
|
};
|
|
1918
|
+
function _toBytes(value) {
|
|
1919
|
+
if (value instanceof Uint8Array) return value;
|
|
1920
|
+
if (typeof value === "string") {
|
|
1921
|
+
try {
|
|
1922
|
+
return base64Decode(value);
|
|
1923
|
+
} catch {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1408
1929
|
|
|
1409
1930
|
// src/rpc-system-call-sender.ts
|
|
1931
|
+
var { errorLog } = getLogs("RpcSystemCallSender");
|
|
1410
1932
|
var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
1411
1933
|
// Reusable envelopes — one per hot-path method.
|
|
1412
1934
|
_itemEnv = {
|
|
@@ -1463,12 +1985,17 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
1463
1985
|
]);
|
|
1464
1986
|
}
|
|
1465
1987
|
ok(conn, format, relatedId, result) {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1988
|
+
try {
|
|
1989
|
+
this._send(
|
|
1990
|
+
conn,
|
|
1991
|
+
format,
|
|
1992
|
+
{ Method: RpcSystemCalls.ok, RelatedId: relatedId },
|
|
1993
|
+
[result]
|
|
1994
|
+
);
|
|
1995
|
+
} catch (e) {
|
|
1996
|
+
errorLog?.log(`Failed to send Ok response for call #${relatedId}`, e);
|
|
1997
|
+
this.error(conn, format, relatedId, e);
|
|
1998
|
+
}
|
|
1472
1999
|
}
|
|
1473
2000
|
error(conn, format, relatedId, error) {
|
|
1474
2001
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1580,19 +2107,19 @@ var RpcSharedObjectTracker = class {
|
|
|
1580
2107
|
};
|
|
1581
2108
|
|
|
1582
2109
|
// src/rpc-peer.ts
|
|
1583
|
-
var
|
|
2110
|
+
var import_core8 = require("@actuallab/core");
|
|
1584
2111
|
|
|
1585
2112
|
// src/rpc-client-peer-reconnect-delayer.ts
|
|
1586
|
-
var
|
|
1587
|
-
var RpcClientPeerReconnectDelayer = class extends
|
|
2113
|
+
var import_core7 = require("@actuallab/core");
|
|
2114
|
+
var RpcClientPeerReconnectDelayer = class extends import_core7.RetryDelayer {
|
|
1588
2115
|
constructor() {
|
|
1589
2116
|
super();
|
|
1590
|
-
this.delays =
|
|
2117
|
+
this.delays = import_core7.RetryDelaySeq.exp(1e3, 6e4);
|
|
1591
2118
|
}
|
|
1592
2119
|
};
|
|
1593
2120
|
|
|
1594
2121
|
// src/rpc-serialization-format.ts
|
|
1595
|
-
var
|
|
2122
|
+
var import_msgpack4 = require("@msgpack/msgpack");
|
|
1596
2123
|
var RpcSerializationFormat = class {
|
|
1597
2124
|
/** Create a reusable binary encoder for this format. Returns undefined for text formats. */
|
|
1598
2125
|
createEncoder() {
|
|
@@ -1666,7 +2193,7 @@ var RpcMessagePackSerializationFormat = class extends RpcSerializationFormat {
|
|
|
1666
2193
|
return createBinaryEncoder();
|
|
1667
2194
|
}
|
|
1668
2195
|
createDecoder() {
|
|
1669
|
-
return new
|
|
2196
|
+
return new import_msgpack4.Decoder();
|
|
1670
2197
|
}
|
|
1671
2198
|
};
|
|
1672
2199
|
var RpcMessagePackCompactSerializationFormat = class extends RpcSerializationFormat {
|
|
@@ -1698,7 +2225,7 @@ var RpcMessagePackCompactSerializationFormat = class extends RpcSerializationFor
|
|
|
1698
2225
|
return createBinaryEncoder();
|
|
1699
2226
|
}
|
|
1700
2227
|
createDecoder() {
|
|
1701
|
-
return new
|
|
2228
|
+
return new import_msgpack4.Decoder();
|
|
1702
2229
|
}
|
|
1703
2230
|
};
|
|
1704
2231
|
RpcSerializationFormat.SystemJsonV5 = new RpcJsonSerializationFormat("json5");
|
|
@@ -1750,6 +2277,7 @@ var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
|
1750
2277
|
|
|
1751
2278
|
// src/rpc-peer.ts
|
|
1752
2279
|
var RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
|
|
2280
|
+
var { debugLog: debugLog2, infoLog, warnLog: warnLog6, errorLog: errorLog2 } = getLogs("RpcPeer");
|
|
1753
2281
|
var defaultConnectionUrlResolver = (peer) => {
|
|
1754
2282
|
const sep = peer.ref.includes("?") ? "&" : "?";
|
|
1755
2283
|
return peer.ref + sep + `clientId=${peer.clientId}&f=${peer.serializationFormat}`;
|
|
@@ -1765,8 +2293,8 @@ var RpcPeer = class {
|
|
|
1765
2293
|
inbound = new RpcInboundCallTracker();
|
|
1766
2294
|
remoteObjects = new RpcRemoteObjectTracker();
|
|
1767
2295
|
sharedObjects = new RpcSharedObjectTracker();
|
|
1768
|
-
connected = new
|
|
1769
|
-
disconnected = new
|
|
2296
|
+
connected = new import_core8.EventHandlerSet();
|
|
2297
|
+
disconnected = new import_core8.EventHandlerSet();
|
|
1770
2298
|
_pendingSends = [];
|
|
1771
2299
|
_keepAliveTimer;
|
|
1772
2300
|
constructor(ref, hub) {
|
|
@@ -1788,16 +2316,29 @@ var RpcPeer = class {
|
|
|
1788
2316
|
conn.closed.add((ev) => {
|
|
1789
2317
|
this._connection = void 0;
|
|
1790
2318
|
this._stopKeepAlive();
|
|
2319
|
+
if (ev.reason)
|
|
2320
|
+
infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);
|
|
2321
|
+
else
|
|
2322
|
+
infoLog?.log(`'${this.ref}': Disconnected`);
|
|
1791
2323
|
this.disconnected.trigger(ev);
|
|
1792
2324
|
});
|
|
1793
2325
|
void conn.whenConnected.then(() => {
|
|
2326
|
+
infoLog?.log(`'${this.ref}': Connected`);
|
|
1794
2327
|
this.connected.trigger();
|
|
1795
2328
|
this._startKeepAlive();
|
|
1796
2329
|
});
|
|
1797
2330
|
}
|
|
1798
|
-
/**
|
|
2331
|
+
/**
|
|
2332
|
+
* The serialization format used by this peer for outgoing messages.
|
|
2333
|
+
* If not set explicitly, defaults to the resolver's default (JSON /
|
|
2334
|
+
* json5np). Subclasses can pre-assign in their constructor.
|
|
2335
|
+
*/
|
|
2336
|
+
_format;
|
|
1799
2337
|
get format() {
|
|
1800
|
-
return RpcSerializationFormatResolver.Default.defaultFormat;
|
|
2338
|
+
return this._format ?? RpcSerializationFormatResolver.Default.defaultFormat;
|
|
2339
|
+
}
|
|
2340
|
+
set format(value) {
|
|
2341
|
+
this._format = value;
|
|
1801
2342
|
}
|
|
1802
2343
|
call(method, args, options) {
|
|
1803
2344
|
const callId = this.outbound.nextId();
|
|
@@ -1857,6 +2398,7 @@ var RpcPeer = class {
|
|
|
1857
2398
|
this._sendWireData(wireData);
|
|
1858
2399
|
}
|
|
1859
2400
|
close() {
|
|
2401
|
+
infoLog?.log(`'${this.ref}': Stopping`);
|
|
1860
2402
|
this._stopKeepAlive();
|
|
1861
2403
|
this.remoteObjects.disconnectAll();
|
|
1862
2404
|
this.sharedObjects.disconnectAll();
|
|
@@ -1870,6 +2412,7 @@ var RpcPeer = class {
|
|
|
1870
2412
|
this.outbound.invalidateAll();
|
|
1871
2413
|
this._connection?.close();
|
|
1872
2414
|
this._hub.peers.delete(this.ref);
|
|
2415
|
+
infoLog?.log(`'${this.ref}': Stopped`);
|
|
1873
2416
|
}
|
|
1874
2417
|
/** Send any messages buffered while disconnected. Call after connection + handshake are ready. */
|
|
1875
2418
|
_flushPendingSends() {
|
|
@@ -1896,18 +2439,32 @@ var RpcPeer = class {
|
|
|
1896
2439
|
_handleMessage(received) {
|
|
1897
2440
|
let message;
|
|
1898
2441
|
let args;
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2442
|
+
try {
|
|
2443
|
+
if (received.kind === "binary") {
|
|
2444
|
+
message = received.message;
|
|
2445
|
+
args = received.args;
|
|
2446
|
+
} else {
|
|
2447
|
+
const parsed = deserializeMessage(received.raw);
|
|
2448
|
+
message = parsed.message;
|
|
2449
|
+
args = parsed.args;
|
|
2450
|
+
}
|
|
2451
|
+
} catch (e) {
|
|
2452
|
+
errorLog2?.log(`'${this.ref}': Failed to process inbound message:`, received, e);
|
|
2453
|
+
return;
|
|
1906
2454
|
}
|
|
1907
2455
|
const method = message.Method ?? "";
|
|
2456
|
+
debugLog2?.log(`'${this.ref}': <- ${method}#${message.RelatedId ?? 0}`);
|
|
1908
2457
|
if (method === RpcSystemCalls.handshake) {
|
|
1909
|
-
const
|
|
1910
|
-
if (
|
|
2458
|
+
const raw = args[0];
|
|
2459
|
+
if (raw !== void 0) {
|
|
2460
|
+
const handshake = Array.isArray(raw) ? {
|
|
2461
|
+
RemotePeerId: raw[0],
|
|
2462
|
+
RemoteHubId: raw[2],
|
|
2463
|
+
ProtocolVersion: raw[3],
|
|
2464
|
+
Index: raw[4]
|
|
2465
|
+
} : raw;
|
|
2466
|
+
this._onHandshakeReceived(handshake);
|
|
2467
|
+
}
|
|
1911
2468
|
return;
|
|
1912
2469
|
}
|
|
1913
2470
|
if (method.startsWith("$sys")) {
|
|
@@ -1963,6 +2520,7 @@ var RpcPeer = class {
|
|
|
1963
2520
|
e
|
|
1964
2521
|
);
|
|
1965
2522
|
}
|
|
2523
|
+
debugLog2?.log(`'${this.ref}': Inbound call failed: ${method}#${relatedId}`, e);
|
|
1966
2524
|
} finally {
|
|
1967
2525
|
if (!isNoWait) {
|
|
1968
2526
|
this.inbound.remove(relatedId);
|
|
@@ -1997,6 +2555,12 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1997
2555
|
_connectionKind = 0 /* Disconnected */;
|
|
1998
2556
|
_tryIndex = 0;
|
|
1999
2557
|
_handshakeIndex = 0;
|
|
2558
|
+
/** Index from the MOST RECENTLY RECEIVED server handshake — sent back
|
|
2559
|
+
* to the server in `$sys.Reconnect:3` so the server can verify we're
|
|
2560
|
+
* talking about the current connection. Mirrors .NET's
|
|
2561
|
+
* `connectionState.Handshake.Index` path in
|
|
2562
|
+
* `RpcOutboundCallTracker.TryReconnect`. */
|
|
2563
|
+
_remoteHandshakeIndex = 0;
|
|
2000
2564
|
_lastRemoteHubId;
|
|
2001
2565
|
_pendingHandshake;
|
|
2002
2566
|
_reconnectsAt = 0;
|
|
@@ -2010,11 +2574,11 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2010
2574
|
serializationFormat;
|
|
2011
2575
|
/** Builds the WebSocket URL for this peer. Replace to customize URL construction. */
|
|
2012
2576
|
connectionUrlResolver = defaultConnectionUrlResolver;
|
|
2013
|
-
peerChanged = new
|
|
2577
|
+
peerChanged = new import_core8.EventHandlerSet();
|
|
2014
2578
|
/** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
|
|
2015
|
-
unsupportedFormat = new
|
|
2579
|
+
unsupportedFormat = new import_core8.EventHandlerSet();
|
|
2016
2580
|
reconnectDelayer = new RpcClientPeerReconnectDelayer();
|
|
2017
|
-
reconnectsAtChanged = new
|
|
2581
|
+
reconnectsAtChanged = new import_core8.EventHandlerSet();
|
|
2018
2582
|
get reconnectsAt() {
|
|
2019
2583
|
return this._reconnectsAt;
|
|
2020
2584
|
}
|
|
@@ -2023,32 +2587,34 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2023
2587
|
this._reconnectsAt = value;
|
|
2024
2588
|
this.reconnectsAtChanged.trigger();
|
|
2025
2589
|
}
|
|
2026
|
-
_format;
|
|
2027
2590
|
constructor(hub, url, serializationFormat) {
|
|
2028
2591
|
super(url, hub);
|
|
2029
2592
|
this.clientId = guidToBase64Url(this.id);
|
|
2030
2593
|
const resolver = RpcSerializationFormatResolver.Default;
|
|
2031
2594
|
this.serializationFormat = serializationFormat ?? resolver.defaultFormatKey;
|
|
2032
|
-
this.
|
|
2033
|
-
}
|
|
2034
|
-
get format() {
|
|
2035
|
-
return this._format;
|
|
2595
|
+
this.format = resolver.get(this.serializationFormat);
|
|
2036
2596
|
}
|
|
2037
2597
|
get connectionKind() {
|
|
2038
2598
|
return this._connectionKind;
|
|
2039
2599
|
}
|
|
2040
|
-
/** One-shot connection for tests — no reconnection loop, no handshake.
|
|
2041
|
-
|
|
2600
|
+
/** One-shot connection for tests — no reconnection loop, no handshake.
|
|
2601
|
+
* @param isPeerChanged Whether to treat this as a peer-identity change
|
|
2602
|
+
* (default `true` — no handshake was exchanged, so stage-3 compute calls
|
|
2603
|
+
* self-invalidate and remote/shared objects are disposed). Pass `false`
|
|
2604
|
+
* to simulate a same-peer reconnect in tests. */
|
|
2605
|
+
connectWith(conn, isPeerChanged = true) {
|
|
2042
2606
|
this.setupConnection(conn);
|
|
2043
|
-
this._reconnect(
|
|
2607
|
+
void this._reconnect(isPeerChanged);
|
|
2044
2608
|
}
|
|
2045
2609
|
/** Start the reconnection loop — runs until disposed. */
|
|
2046
2610
|
async run(wsFactory) {
|
|
2047
2611
|
const connUrl = this.connectionUrlResolver(this);
|
|
2612
|
+
infoLog?.log(`'${this.ref}': Started (Client)`);
|
|
2048
2613
|
while (!this._disposed) {
|
|
2049
2614
|
let lastCloseCode = 0;
|
|
2050
2615
|
let lastCloseReason = "";
|
|
2051
2616
|
try {
|
|
2617
|
+
infoLog?.log(`'${this.ref}': Connecting to ${connUrl}`);
|
|
2052
2618
|
const ws = wsFactory?.(connUrl) ?? new WebSocket(connUrl);
|
|
2053
2619
|
const conn = new RpcWebSocketConnection(
|
|
2054
2620
|
ws,
|
|
@@ -2061,7 +2627,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2061
2627
|
lastCloseCode = ev.code;
|
|
2062
2628
|
lastCloseReason = ev.reason;
|
|
2063
2629
|
});
|
|
2064
|
-
this._pendingHandshake = new
|
|
2630
|
+
this._pendingHandshake = new import_core8.PromiseSource();
|
|
2065
2631
|
this.setupConnection(conn);
|
|
2066
2632
|
this._connection = void 0;
|
|
2067
2633
|
const closedRejection = new Promise(
|
|
@@ -2072,38 +2638,48 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2072
2638
|
closedRejection.catch(() => {
|
|
2073
2639
|
});
|
|
2074
2640
|
await Promise.race([conn.whenConnected, closedRejection]);
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2641
|
+
try {
|
|
2642
|
+
this._hub.systemCallSender.handshake(
|
|
2643
|
+
conn,
|
|
2644
|
+
this.format,
|
|
2645
|
+
this.id,
|
|
2646
|
+
this._hub.hubId,
|
|
2647
|
+
++this._handshakeIndex
|
|
2648
|
+
);
|
|
2649
|
+
} catch (e) {
|
|
2650
|
+
warnLog6?.log(`'${this.ref}': Failed to send handshake`, e);
|
|
2651
|
+
throw e;
|
|
2652
|
+
}
|
|
2082
2653
|
const remoteHandshake = await Promise.race([
|
|
2083
2654
|
this._pendingHandshake.promise,
|
|
2084
2655
|
closedRejection
|
|
2085
2656
|
]);
|
|
2086
2657
|
this._pendingHandshake = void 0;
|
|
2658
|
+
this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
|
|
2087
2659
|
const remoteHubId = remoteHandshake.RemoteHubId;
|
|
2088
2660
|
let isPeerChanged = false;
|
|
2089
2661
|
if (remoteHubId !== void 0) {
|
|
2090
2662
|
isPeerChanged = this._lastRemoteHubId !== void 0 && this._lastRemoteHubId !== remoteHubId;
|
|
2091
2663
|
if (isPeerChanged) {
|
|
2092
2664
|
this.inbound.clear();
|
|
2665
|
+
infoLog?.log(`'${this.ref}': Peer changed`);
|
|
2093
2666
|
this.peerChanged.trigger();
|
|
2094
2667
|
}
|
|
2095
2668
|
this._lastRemoteHubId = remoteHubId;
|
|
2096
2669
|
}
|
|
2670
|
+
infoLog?.log(`'${this.ref}': Handshake succeeded, peerChanged=${isPeerChanged}`);
|
|
2097
2671
|
this._connection = conn;
|
|
2098
2672
|
this._connectionKind = 2 /* Connected */;
|
|
2099
2673
|
this._tryIndex = 0;
|
|
2100
|
-
this._reconnect(isPeerChanged);
|
|
2674
|
+
await this._reconnect(isPeerChanged);
|
|
2101
2675
|
await new Promise((r) => conn.closed.add(() => r()));
|
|
2102
|
-
} catch {
|
|
2676
|
+
} catch (e) {
|
|
2677
|
+
debugLog2?.log(`'${this.ref}': run() iteration failed:`, e);
|
|
2103
2678
|
}
|
|
2104
2679
|
this._connectionKind = 0 /* Disconnected */;
|
|
2105
2680
|
if (this._disposed) break;
|
|
2106
2681
|
if (lastCloseCode === RPC_CLOSE_CODE_UNSUPPORTED_FORMAT) {
|
|
2682
|
+
warnLog6?.log(`'${this.ref}': Unsupported serialization format - shutting down`);
|
|
2107
2683
|
this.unsupportedFormat.trigger({ reason: lastCloseReason });
|
|
2108
2684
|
this._disposed = true;
|
|
2109
2685
|
break;
|
|
@@ -2111,6 +2687,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2111
2687
|
this._tryIndex++;
|
|
2112
2688
|
const delay = this.reconnectDelayer.getDelay(this._tryIndex);
|
|
2113
2689
|
if (delay.isLimitExceeded) {
|
|
2690
|
+
warnLog6?.log(`'${this.ref}': Reconnect limit exceeded - shutting down`);
|
|
2114
2691
|
this._disposed = true;
|
|
2115
2692
|
break;
|
|
2116
2693
|
}
|
|
@@ -2122,42 +2699,129 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2122
2699
|
}
|
|
2123
2700
|
}
|
|
2124
2701
|
}
|
|
2125
|
-
/**
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2130
|
-
*
|
|
2131
|
-
*
|
|
2132
|
-
|
|
2702
|
+
/**
|
|
2703
|
+
* Reconcile outbound calls after reconnection, then flush pending sends.
|
|
2704
|
+
*
|
|
2705
|
+
* On peer change (different server hubId) or with binary wire format —
|
|
2706
|
+
* we blind-resend every eligible call.
|
|
2707
|
+
*
|
|
2708
|
+
* On a same-peer reconnect with JSON transport, first call
|
|
2709
|
+
* `$sys.Reconnect` to ask the server which call IDs it no longer
|
|
2710
|
+
* recognizes (e.g. inbound tracker was cleared, or the call never
|
|
2711
|
+
* reached the server). Only those get resent; the rest stay in the
|
|
2712
|
+
* tracker awaiting their $sys.Ok. This prevents duplicate server-side
|
|
2713
|
+
* invocation of in-flight streaming calls on every transient
|
|
2714
|
+
* reconnect — mirrors .NET `RpcOutboundCallTracker.Reconnect` at
|
|
2715
|
+
* src/ActualLab.Rpc/Infrastructure/RpcCallTrackers.cs:209-279.
|
|
2716
|
+
*
|
|
2717
|
+
* Stage-3 compute calls (result already resolved, awaiting invalidation)
|
|
2718
|
+
* are always self-invalidated — TS doesn't track compute-call stages
|
|
2719
|
+
* across reconnect, so the safest behavior is to force a fresh compute.
|
|
2720
|
+
*/
|
|
2721
|
+
async _reconnect(isPeerChanged) {
|
|
2133
2722
|
if (this._connection === void 0) return;
|
|
2134
|
-
if (
|
|
2723
|
+
if (isPeerChanged) {
|
|
2135
2724
|
this.remoteObjects.disconnectAll();
|
|
2725
|
+
this.sharedObjects.disconnectAll();
|
|
2136
2726
|
} else {
|
|
2137
2727
|
this.remoteObjects.reconnectAll();
|
|
2138
2728
|
}
|
|
2139
|
-
const
|
|
2140
|
-
for (const call of
|
|
2729
|
+
const eligible = [];
|
|
2730
|
+
for (const call of [...this.outbound.values()]) {
|
|
2141
2731
|
const mode = call.remoteExecutionMode;
|
|
2142
2732
|
if (!(mode & 2)) {
|
|
2143
2733
|
if (!call.result.isCompleted)
|
|
2144
|
-
call.result.reject(new Error(
|
|
2734
|
+
call.result.reject(new Error(
|
|
2735
|
+
"Outbound call failed: disconnected and AllowReconnect is not set."
|
|
2736
|
+
));
|
|
2145
2737
|
call.onDisconnect();
|
|
2146
2738
|
this.outbound.remove(call.callId);
|
|
2147
|
-
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
if (isPeerChanged && !(mode & 4)) {
|
|
2148
2742
|
if (!call.result.isCompleted)
|
|
2149
|
-
call.result.reject(new Error(
|
|
2743
|
+
call.result.reject(new Error(
|
|
2744
|
+
"Outbound call failed: reconnected to a different peer and AllowResend is not set."
|
|
2745
|
+
));
|
|
2150
2746
|
call.onDisconnect();
|
|
2151
2747
|
this.outbound.remove(call.callId);
|
|
2152
|
-
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
eligible.push(call);
|
|
2751
|
+
}
|
|
2752
|
+
let unknownIds = null;
|
|
2753
|
+
if (!isPeerChanged && eligible.length > 0) {
|
|
2754
|
+
unknownIds = await this._reconcileReconnect(eligible);
|
|
2755
|
+
}
|
|
2756
|
+
for (const call of eligible) {
|
|
2757
|
+
if (!call.removeOnOk && call.result.isCompleted) {
|
|
2153
2758
|
call.onDisconnect();
|
|
2154
2759
|
this.outbound.remove(call.callId);
|
|
2155
|
-
|
|
2156
|
-
|
|
2760
|
+
continue;
|
|
2761
|
+
}
|
|
2762
|
+
if (unknownIds !== null && !unknownIds.has(call.callId)) {
|
|
2763
|
+
continue;
|
|
2157
2764
|
}
|
|
2765
|
+
this._sendWireData(call.serializedWireData);
|
|
2158
2766
|
}
|
|
2159
2767
|
this._flushPendingSends();
|
|
2160
2768
|
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Ask the server which call IDs in `eligible` it no longer recognizes
|
|
2771
|
+
* via `$sys.Reconnect:3`. Returns the set of call IDs that need to be
|
|
2772
|
+
* resent. On any failure, falls back to "resend everything".
|
|
2773
|
+
*
|
|
2774
|
+
* Wire format of the `completedStages` argument is chosen per wire format:
|
|
2775
|
+
* - JSON (json5np): plain object `{"<stage>": "<base64-of-bytes>"}` —
|
|
2776
|
+
* matches .NET `System.Text.Json`'s default for `Dictionary<int, byte[]>`.
|
|
2777
|
+
* - MessagePack (msgpack6/msgpack6c): `Map<number, Uint8Array>` —
|
|
2778
|
+
* the patched encoder (see `./msgpack-map-patch.ts`) writes this as a
|
|
2779
|
+
* native msgpack `map<int, bin>`, matching .NET MessagePack-CSharp's
|
|
2780
|
+
* default for `Dictionary<int, byte[]>`.
|
|
2781
|
+
*/
|
|
2782
|
+
async _reconcileReconnect(eligible) {
|
|
2783
|
+
try {
|
|
2784
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
2785
|
+
for (const call of eligible) {
|
|
2786
|
+
const stage = call.getReconnectStage(false);
|
|
2787
|
+
if (stage === null) continue;
|
|
2788
|
+
let arr = byStage.get(stage);
|
|
2789
|
+
if (!arr) {
|
|
2790
|
+
arr = [];
|
|
2791
|
+
byStage.set(stage, arr);
|
|
2792
|
+
}
|
|
2793
|
+
arr.push(call.callId);
|
|
2794
|
+
}
|
|
2795
|
+
if (byStage.size === 0) {
|
|
2796
|
+
return new Set(eligible.map((c) => c.callId));
|
|
2797
|
+
}
|
|
2798
|
+
const isBinary = this.format.isBinary;
|
|
2799
|
+
const stagesArg = isBinary ? /* @__PURE__ */ new Map() : {};
|
|
2800
|
+
for (const [stage, ids] of byStage) {
|
|
2801
|
+
ids.sort((a, b) => a - b);
|
|
2802
|
+
const bytes2 = IncreasingSeqCompressor.serialize(ids);
|
|
2803
|
+
if (isBinary) {
|
|
2804
|
+
stagesArg.set(stage, bytes2);
|
|
2805
|
+
} else {
|
|
2806
|
+
stagesArg[String(stage)] = base64Encode(bytes2);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
const reconnectCall = this.call(
|
|
2810
|
+
RpcSystemCalls.reconnect,
|
|
2811
|
+
[this._remoteHandshakeIndex, stagesArg],
|
|
2812
|
+
{
|
|
2813
|
+
remoteExecutionMode: 1
|
|
2814
|
+
/* AwaitForConnection only; must not itself reconnect */
|
|
2815
|
+
}
|
|
2816
|
+
);
|
|
2817
|
+
const result = await reconnectCall.result.promise;
|
|
2818
|
+
const bytes = _toBytes2(result);
|
|
2819
|
+
if (bytes === null) return new Set(eligible.map((c) => c.callId));
|
|
2820
|
+
return new Set(IncreasingSeqCompressor.deserialize(bytes));
|
|
2821
|
+
} catch {
|
|
2822
|
+
return new Set(eligible.map((c) => c.callId));
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2161
2825
|
_onHandshakeReceived(handshake) {
|
|
2162
2826
|
this._pendingHandshake?.resolve(handshake);
|
|
2163
2827
|
}
|
|
@@ -2205,8 +2869,20 @@ function guidToBase64Url(uuid) {
|
|
|
2205
2869
|
const binary = String.fromCharCode(...bytes);
|
|
2206
2870
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2207
2871
|
}
|
|
2872
|
+
function _toBytes2(value) {
|
|
2873
|
+
if (value instanceof Uint8Array) return value;
|
|
2874
|
+
if (typeof value === "string") {
|
|
2875
|
+
try {
|
|
2876
|
+
return base64Decode(value);
|
|
2877
|
+
} catch {
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2208
2883
|
|
|
2209
2884
|
// src/rpc-service-host.ts
|
|
2885
|
+
var { warnLog: warnLog7 } = getLogs("RpcServiceHost");
|
|
2210
2886
|
var RpcServiceHost = class {
|
|
2211
2887
|
_methods = /* @__PURE__ */ new Map();
|
|
2212
2888
|
register(def, impl) {
|
|
@@ -2221,8 +2897,10 @@ var RpcServiceHost = class {
|
|
|
2221
2897
|
}
|
|
2222
2898
|
async dispatch(wireMethod, args, context) {
|
|
2223
2899
|
const entry = this._methods.get(wireMethod);
|
|
2224
|
-
if (entry === void 0)
|
|
2900
|
+
if (entry === void 0) {
|
|
2901
|
+
warnLog7?.log(`Method not found: ${wireMethod}`);
|
|
2225
2902
|
throw new Error(`Method not found: ${wireMethod}`);
|
|
2903
|
+
}
|
|
2226
2904
|
if (context !== void 0 && entry.def.callTypeId !== 0) {
|
|
2227
2905
|
return await entry.fn(...args, context);
|
|
2228
2906
|
}
|
|
@@ -2752,6 +3430,7 @@ function computeMethodHash(methodName) {
|
|
|
2752
3430
|
}
|
|
2753
3431
|
|
|
2754
3432
|
// src/rpc-method-registry.ts
|
|
3433
|
+
var { warnLog: warnLog8 } = getLogs("RpcMethodRegistry");
|
|
2755
3434
|
var RpcMethodRegistry = class {
|
|
2756
3435
|
_nameToHash = /* @__PURE__ */ new Map();
|
|
2757
3436
|
_hashToName = /* @__PURE__ */ new Map();
|
|
@@ -2761,9 +3440,7 @@ var RpcMethodRegistry = class {
|
|
|
2761
3440
|
const hash = computeMethodHash(methodName);
|
|
2762
3441
|
const existing = this._hashToName.get(hash);
|
|
2763
3442
|
if (existing !== void 0 && existing !== methodName) {
|
|
2764
|
-
|
|
2765
|
-
`[RpcMethodRegistry] hash collision: "${methodName}" and "${existing}" both hash to ${hash}`
|
|
2766
|
-
);
|
|
3443
|
+
warnLog8?.log(`hash collision: "${methodName}" and "${existing}" both hash to ${hash}`);
|
|
2767
3444
|
return;
|
|
2768
3445
|
}
|
|
2769
3446
|
this._nameToHash.set(methodName, hash);
|
|
@@ -2796,6 +3473,7 @@ var RpcMethodRegistry = class {
|
|
|
2796
3473
|
};
|
|
2797
3474
|
|
|
2798
3475
|
// src/rpc-hub.ts
|
|
3476
|
+
var { warnLog: warnLog9 } = getLogs("RpcHub");
|
|
2799
3477
|
var RpcHub = class {
|
|
2800
3478
|
hubId;
|
|
2801
3479
|
peers = /* @__PURE__ */ new Map();
|
|
@@ -2821,6 +3499,7 @@ var RpcHub = class {
|
|
|
2821
3499
|
if (peer !== void 0) {
|
|
2822
3500
|
peer.close();
|
|
2823
3501
|
this.peers.delete(ref);
|
|
3502
|
+
warnLog9?.log(`'${ref}': peer is removed from RpcHub`);
|
|
2824
3503
|
}
|
|
2825
3504
|
}
|
|
2826
3505
|
/** Get or create a peer by ref. Server peers have "server://" prefix, everything else is a client peer. */
|
|
@@ -3052,13 +3731,14 @@ function getStateDescription(state, useLastError = false) {
|
|
|
3052
3731
|
}
|
|
3053
3732
|
|
|
3054
3733
|
// src/rpc-peer-state-monitor.ts
|
|
3055
|
-
var
|
|
3734
|
+
var import_core9 = require("@actuallab/core");
|
|
3735
|
+
var { infoLog: infoLog2 } = getLogs("RpcPeerStateMonitor");
|
|
3056
3736
|
var JustConnectedPeriodMs = 1500;
|
|
3057
3737
|
var JustDisconnectedPeriodMs = 3e3;
|
|
3058
3738
|
var MinReconnectsInMs = 1e3;
|
|
3059
3739
|
var RpcPeerStateMonitor = class {
|
|
3060
3740
|
peer;
|
|
3061
|
-
stateChanged = new
|
|
3741
|
+
stateChanged = new import_core9.EventHandlerSet();
|
|
3062
3742
|
_rawConnected;
|
|
3063
3743
|
_connectedAt = 0;
|
|
3064
3744
|
_disconnectedAt = 0;
|
|
@@ -3114,6 +3794,7 @@ var RpcPeerStateMonitor = class {
|
|
|
3114
3794
|
this._scheduleRecompute(
|
|
3115
3795
|
this._rawConnected ? JustConnectedPeriodMs : JustDisconnectedPeriodMs
|
|
3116
3796
|
);
|
|
3797
|
+
infoLog2?.log(`'${peer.ref}': monitor started`);
|
|
3117
3798
|
}
|
|
3118
3799
|
get state() {
|
|
3119
3800
|
return this._state;
|
|
@@ -3129,6 +3810,7 @@ var RpcPeerStateMonitor = class {
|
|
|
3129
3810
|
this.peer.reconnectDelayer.cancelDelaysChanged.remove(
|
|
3130
3811
|
this._onCancelDelaysChanged
|
|
3131
3812
|
);
|
|
3813
|
+
infoLog2?.log(`'${this.peer.ref}': monitor stopped`);
|
|
3132
3814
|
}
|
|
3133
3815
|
_recompute() {
|
|
3134
3816
|
if (this._timer !== void 0) {
|
|
@@ -3208,7 +3890,9 @@ var RpcPeerStateMonitor = class {
|
|
|
3208
3890
|
ARG_DELIMITER,
|
|
3209
3891
|
ENVELOPE_DELIMITER,
|
|
3210
3892
|
FRAME_DELIMITER,
|
|
3893
|
+
IncreasingSeqCompressor,
|
|
3211
3894
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
3895
|
+
RpcCallStage,
|
|
3212
3896
|
RpcCallTypeId,
|
|
3213
3897
|
RpcClientPeer,
|
|
3214
3898
|
RpcClientPeerReconnectDelayer,
|
|
@@ -3253,6 +3937,7 @@ var RpcPeerStateMonitor = class {
|
|
|
3253
3937
|
deserializeBinaryMessage,
|
|
3254
3938
|
deserializeCompactBinaryMessage,
|
|
3255
3939
|
deserializeMessage,
|
|
3940
|
+
getLogs,
|
|
3256
3941
|
getMethodsMeta,
|
|
3257
3942
|
getServiceMeta,
|
|
3258
3943
|
getStateDescription,
|