@fluidframework/container-runtime 2.3.0-288113 → 2.4.0-294316
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/CHANGELOG.md +15 -0
- package/api-report/container-runtime.legacy.alpha.api.md +15 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +1 -16
- package/dist/channelCollection.js.map +1 -1
- package/dist/connectionTelemetry.d.ts +27 -3
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +68 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +262 -180
- package/dist/containerRuntime.js.map +1 -1
- package/dist/deltaManagerProxies.d.ts.map +1 -1
- package/dist/deltaManagerProxies.js +11 -4
- package/dist/deltaManagerProxies.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +0 -2
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +0 -8
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +3 -1
- package/dist/messageTypes.d.ts +0 -9
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +9 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +19 -6
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/duplicateBatchDetector.d.ts +32 -0
- package/dist/opLifecycle/duplicateBatchDetector.d.ts.map +1 -0
- package/dist/opLifecycle/duplicateBatchDetector.js +68 -0
- package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +3 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +4 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +0 -4
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +0 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +1 -6
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +1 -4
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +37 -17
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +47 -37
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +27 -17
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +85 -56
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts +2 -4
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +6 -37
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +0 -2
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js +5 -7
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +1 -4
- package/dist/summary/summaryFormat.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +1 -16
- package/lib/channelCollection.js.map +1 -1
- package/lib/connectionTelemetry.d.ts +27 -3
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +68 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +262 -181
- package/lib/containerRuntime.js.map +1 -1
- package/lib/deltaManagerProxies.d.ts.map +1 -1
- package/lib/deltaManagerProxies.js +11 -4
- package/lib/deltaManagerProxies.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +0 -2
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +0 -8
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +3 -1
- package/lib/messageTypes.d.ts +0 -9
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +9 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +17 -5
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/duplicateBatchDetector.d.ts +32 -0
- package/lib/opLifecycle/duplicateBatchDetector.d.ts.map +1 -0
- package/lib/opLifecycle/duplicateBatchDetector.js +64 -0
- package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +3 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +2 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +0 -4
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +0 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +1 -6
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +1 -4
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +37 -17
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +47 -37
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +27 -17
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +85 -56
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts +2 -4
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +6 -37
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +0 -2
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js +5 -7
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +1 -4
- package/lib/summary/summaryFormat.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +26 -25
- package/src/channelCollection.ts +7 -21
- package/src/connectionTelemetry.ts +33 -3
- package/src/containerRuntime.ts +382 -233
- package/src/deltaManagerProxies.ts +11 -4
- package/src/gc/garbageCollection.ts +1 -3
- package/src/gc/gcHelpers.ts +4 -12
- package/src/index.ts +2 -0
- package/src/messageTypes.ts +0 -10
- package/src/opLifecycle/batchManager.ts +29 -7
- package/src/opLifecycle/duplicateBatchDetector.ts +78 -0
- package/src/opLifecycle/index.ts +4 -1
- package/src/opLifecycle/opCompressor.ts +2 -6
- package/src/opLifecycle/opGroupingManager.ts +2 -6
- package/src/opLifecycle/opSplitter.ts +2 -6
- package/src/opLifecycle/outbox.ts +1 -3
- package/src/opLifecycle/remoteMessageProcessor.ts +87 -59
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +114 -66
- package/src/scheduleManager.ts +8 -47
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +1 -3
- package/src/summary/summaryCollection.ts +7 -9
- package/src/summary/summaryFormat.ts +1 -3
- package/src/summary/summaryFormats.md +11 -9
- package/tsconfig.json +1 -0
package/lib/containerRuntime.js
CHANGED
|
@@ -11,7 +11,7 @@ import { DriverHeader, FetchSource, MessageType, } from "@fluidframework/driver-
|
|
|
11
11
|
import { readAndParse } from "@fluidframework/driver-utils/internal";
|
|
12
12
|
import { FlushMode, FlushModeExperimental, channelsTreeName, gcTreeKey, } from "@fluidframework/runtime-definitions/internal";
|
|
13
13
|
import { GCDataBuilder, RequestParser, TelemetryContext, addBlobToSummary, addSummarizeResultToSummary, calculateStats, create404Response, exceptionToResponse, responseToException, seqFromTree, } from "@fluidframework/runtime-utils/internal";
|
|
14
|
-
import { DataCorruptionError, DataProcessingError, GenericError, LoggingError, PerformanceEvent,
|
|
14
|
+
import { DataCorruptionError, DataProcessingError, extractSafePropertiesFromMessage, GenericError, LoggingError, PerformanceEvent,
|
|
15
15
|
// eslint-disable-next-line import/no-deprecated
|
|
16
16
|
TaggedLoggerAdapter, UsageError, createChildLogger, createChildMonitoringContext, createSampledLogger, loggerToMonitoringContext, raiseConnectedEvent, wrapError, tagCodeArtifacts, } from "@fluidframework/telemetry-utils/internal";
|
|
17
17
|
import { v4 as uuid } from "uuid";
|
|
@@ -25,7 +25,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
|
|
|
25
25
|
import { DeltaManagerPendingOpsProxy, DeltaManagerSummarizerProxy, } from "./deltaManagerProxies.js";
|
|
26
26
|
import { GCNodeType, GarbageCollector, gcGenerationOptionName, } from "./gc/index.js";
|
|
27
27
|
import { ContainerMessageType, } from "./messageTypes.js";
|
|
28
|
-
import { ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
|
|
28
|
+
import { DuplicateBatchDetector, ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
|
|
29
29
|
import { pkgVersion } from "./packageVersion.js";
|
|
30
30
|
import { PendingStateManager, } from "./pendingStateManager.js";
|
|
31
31
|
import { ScheduleManager } from "./scheduleManager.js";
|
|
@@ -228,15 +228,25 @@ function lastMessageFromMetadata(metadata) {
|
|
|
228
228
|
* We only want to log this once, to avoid spamming telemetry if we are wrong and these cases are hit commonly.
|
|
229
229
|
*/
|
|
230
230
|
let getSingleUseLegacyLogCallback = (logger, type) => {
|
|
231
|
-
// We only want to log this once per ContainerRuntime instance, to avoid spamming telemetry.
|
|
232
|
-
getSingleUseLegacyLogCallback = () => () => { };
|
|
233
231
|
return (codePath) => {
|
|
234
232
|
logger.sendTelemetryEvent({
|
|
235
233
|
eventName: "LegacyMessageFormat",
|
|
236
234
|
details: { codePath, type },
|
|
237
235
|
});
|
|
236
|
+
// Now that we've logged, prevent future logging (globally).
|
|
237
|
+
getSingleUseLegacyLogCallback = () => () => { };
|
|
238
238
|
};
|
|
239
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
|
|
242
|
+
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
|
|
243
|
+
* @returns A runtime which provides all the functionality necessary to bind with the loader layer via the {@link @fluidframework/container-definitions#IRuntime} interface and provide a runtime environment via the {@link @fluidframework/container-runtime-definitions#IContainerRuntime} interface.
|
|
244
|
+
* @legacy
|
|
245
|
+
* @alpha
|
|
246
|
+
*/
|
|
247
|
+
export async function loadContainerRuntime(params) {
|
|
248
|
+
return ContainerRuntime.loadRuntime(params);
|
|
249
|
+
}
|
|
240
250
|
/**
|
|
241
251
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
242
252
|
* It will define the store level mappings.
|
|
@@ -588,11 +598,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
588
598
|
this._disposed = false;
|
|
589
599
|
this.emitDirtyDocumentEvent = true;
|
|
590
600
|
this.defaultTelemetrySignalSampleCount = 100;
|
|
591
|
-
this.
|
|
601
|
+
this._signalTracking = {
|
|
602
|
+
totalSignalsSentInLatencyWindow: 0,
|
|
592
603
|
signalsLost: 0,
|
|
593
|
-
|
|
604
|
+
signalsOutOfOrder: 0,
|
|
605
|
+
signalsSentSinceLastLatencyMeasurement: 0,
|
|
606
|
+
broadcastSignalSequenceNumber: 0,
|
|
594
607
|
signalTimestamp: 0,
|
|
608
|
+
roundTripSignalSequenceNumber: undefined,
|
|
595
609
|
trackingSignalSequenceNumber: undefined,
|
|
610
|
+
minimumTrackingSignalSequenceNumber: undefined,
|
|
596
611
|
};
|
|
597
612
|
/**
|
|
598
613
|
* It a cache for holding mapping for loading groupIds with its snapshot from the service. Add expiry policy of 1 minute.
|
|
@@ -700,13 +715,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
700
715
|
isAttached: () => this.attachState !== AttachState.Detached,
|
|
701
716
|
}, pendingRuntimeState?.pending, this.logger);
|
|
702
717
|
let outerDeltaManager;
|
|
703
|
-
|
|
718
|
+
this.useDeltaManagerOpsProxy =
|
|
719
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true;
|
|
704
720
|
// The summarizerDeltaManager Proxy is used to lie to the summarizer to convince it is in the right state as a summarizer client.
|
|
705
721
|
const summarizerDeltaManagerProxy = new DeltaManagerSummarizerProxy(this.innerDeltaManager);
|
|
706
722
|
outerDeltaManager = summarizerDeltaManagerProxy;
|
|
707
723
|
// The DeltaManagerPendingOpsProxy is used to control the minimum sequence number
|
|
708
724
|
// It allows us to lie to the layers below so that they can maintain enough local state for rebasing ops.
|
|
709
|
-
if (useDeltaManagerOpsProxy) {
|
|
725
|
+
if (this.useDeltaManagerOpsProxy) {
|
|
710
726
|
const pendingOpsDeltaManagerProxy = new DeltaManagerPendingOpsProxy(summarizerDeltaManagerProxy, this.pendingStateManager);
|
|
711
727
|
outerDeltaManager = pendingOpsDeltaManagerProxy;
|
|
712
728
|
}
|
|
@@ -737,6 +753,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
737
753
|
this.closeFn(error);
|
|
738
754
|
throw error;
|
|
739
755
|
}
|
|
756
|
+
// DuplicateBatchDetection is only enabled if Offline Load is enabled
|
|
757
|
+
// It maintains a cache of all batchIds/sequenceNumbers within the collab window.
|
|
758
|
+
// Don't waste resources doing so if not needed.
|
|
759
|
+
if (this.offlineEnabled) {
|
|
760
|
+
this.duplicateBatchDetector = new DuplicateBatchDetector();
|
|
761
|
+
}
|
|
740
762
|
if (context.attachState === AttachState.Attached) {
|
|
741
763
|
const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
|
|
742
764
|
if (maxSnapshotCacheDurationMs !== undefined &&
|
|
@@ -1134,13 +1156,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1134
1156
|
let childTree = snapshotTree;
|
|
1135
1157
|
for (const part of pathParts) {
|
|
1136
1158
|
if (hasIsolatedChannels) {
|
|
1137
|
-
|
|
1138
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1139
|
-
childTree = childTree.trees[channelsTreeName];
|
|
1159
|
+
childTree = childTree?.trees[channelsTreeName];
|
|
1140
1160
|
}
|
|
1141
|
-
|
|
1142
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1143
|
-
childTree = childTree.trees[part];
|
|
1161
|
+
childTree = childTree?.trees[part];
|
|
1144
1162
|
}
|
|
1145
1163
|
return childTree;
|
|
1146
1164
|
}
|
|
@@ -1187,8 +1205,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1187
1205
|
return this.resolveHandle(requestParser.createSubRequest(1));
|
|
1188
1206
|
}
|
|
1189
1207
|
if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
|
|
1190
|
-
// TODO why are we non null asserting here?
|
|
1191
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1192
1208
|
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
1193
1209
|
return blob
|
|
1194
1210
|
? {
|
|
@@ -1461,9 +1477,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1461
1477
|
}
|
|
1462
1478
|
this._connected = connected;
|
|
1463
1479
|
if (!connected) {
|
|
1464
|
-
this.
|
|
1465
|
-
this.
|
|
1466
|
-
this.
|
|
1480
|
+
this._signalTracking.signalsLost = 0;
|
|
1481
|
+
this._signalTracking.signalsOutOfOrder = 0;
|
|
1482
|
+
this._signalTracking.signalTimestamp = 0;
|
|
1483
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
1484
|
+
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
1485
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
1486
|
+
this._signalTracking.trackingSignalSequenceNumber = undefined;
|
|
1487
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
|
|
1467
1488
|
}
|
|
1468
1489
|
else {
|
|
1469
1490
|
assert(this.attachState === AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
@@ -1510,158 +1531,159 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1510
1531
|
if (hasModernRuntimeMessageEnvelope) {
|
|
1511
1532
|
// If the message has the modern message envelope, then process it here.
|
|
1512
1533
|
// Here we unpack the message (decompress, unchunk, and/or ungroup) into a batch of messages with ContainerMessageType
|
|
1513
|
-
const
|
|
1514
|
-
if (
|
|
1534
|
+
const inboundResult = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
|
|
1535
|
+
if (inboundResult === undefined) {
|
|
1515
1536
|
// This means the incoming message is an incomplete part of a message or batch
|
|
1516
1537
|
// and we need to process more messages before the rest of the system can understand it.
|
|
1517
1538
|
return;
|
|
1518
1539
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1540
|
+
if ("batchStart" in inboundResult) {
|
|
1541
|
+
const batchStart = inboundResult.batchStart;
|
|
1542
|
+
const result = this.duplicateBatchDetector?.processInboundBatch(batchStart);
|
|
1543
|
+
if (result?.duplicate) {
|
|
1544
|
+
const error = new DataCorruptionError("Duplicate batch - The same batch was sequenced twice", { batchId: batchStart.batchId });
|
|
1545
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1546
|
+
eventName: "DuplicateBatch",
|
|
1547
|
+
details: {
|
|
1548
|
+
batchId: batchStart.batchId,
|
|
1549
|
+
clientId: batchStart.clientId,
|
|
1550
|
+
batchStartCsn: batchStart.batchStartCsn,
|
|
1551
|
+
size: inboundResult.length,
|
|
1552
|
+
duplicateBatchSequenceNumber: result.otherSequenceNumber,
|
|
1553
|
+
...extractSafePropertiesFromMessage(batchStart.keyMessage),
|
|
1554
|
+
},
|
|
1555
|
+
}, error);
|
|
1556
|
+
throw error;
|
|
1557
|
+
}
|
|
1532
1558
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1559
|
+
let runtimeBatch = true;
|
|
1560
|
+
// Reach out to PendingStateManager, either to zip localOpMetadata into the *local* message list,
|
|
1561
|
+
// or to check to ensure the *remote* messages don't match the batchId of a pending local batch.
|
|
1562
|
+
// This latter case would indicate that the container has forked - two copies are trying to persist the same local changes.
|
|
1563
|
+
let messagesWithPendingState = this.pendingStateManager.processInboundMessages(inboundResult, local);
|
|
1564
|
+
if (inboundResult.type !== "fullBatch") {
|
|
1565
|
+
assert(messagesWithPendingState.length === 1, 0xa3d /* Partial batch should have exactly one message */);
|
|
1566
|
+
}
|
|
1567
|
+
if (messagesWithPendingState.length === 0) {
|
|
1568
|
+
assert(inboundResult.type === "fullBatch", 0xa3e /* Empty batch is always considered a full batch */);
|
|
1569
|
+
/**
|
|
1570
|
+
* We need to process an empty batch, which will execute expected actions while processing even if there
|
|
1571
|
+
* are no inner runtime messages.
|
|
1572
|
+
*
|
|
1573
|
+
* Empty batches are produced by the outbox on resubmit when the resubmit flow resulted in no runtime
|
|
1574
|
+
* messages.
|
|
1575
|
+
* This can happen if changes from a remote client "cancel out" the pending changes being resubmitted by
|
|
1576
|
+
* this client. We submit an empty batch if "offline load" (aka rehydrating from stashed state) is
|
|
1577
|
+
* enabled, to ensure we account for this batch when comparing batchIds, checking for a forked container.
|
|
1578
|
+
* Otherwise, we would not realize this container has forked in the case where it did fork, and a batch
|
|
1579
|
+
* became empty but wasn't submitted as such.
|
|
1580
|
+
*/
|
|
1581
|
+
messagesWithPendingState = [
|
|
1582
|
+
{
|
|
1583
|
+
message: inboundResult.batchStart.keyMessage,
|
|
1584
|
+
localOpMetadata: undefined,
|
|
1585
|
+
},
|
|
1586
|
+
];
|
|
1587
|
+
// Empty batch message is a non-runtime message as it was generated by the op grouping manager.
|
|
1588
|
+
runtimeBatch = false;
|
|
1535
1589
|
}
|
|
1590
|
+
const locationInBatch = inboundResult.type === "fullBatch"
|
|
1591
|
+
? { batchStart: true, batchEnd: true }
|
|
1592
|
+
: inboundResult.type === "batchStartingMessage"
|
|
1593
|
+
? { batchStart: true, batchEnd: false }
|
|
1594
|
+
: { batchStart: false, batchEnd: inboundResult.batchEnd === true };
|
|
1595
|
+
this.processInboundMessages(messagesWithPendingState, locationInBatch, local, savedOp, runtimeBatch);
|
|
1536
1596
|
}
|
|
1537
1597
|
else {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
savedOp,
|
|
1547
|
-
}));
|
|
1548
|
-
}
|
|
1549
|
-
else {
|
|
1550
|
-
// A non container runtime message (like other system ops - join, ack, leave, nack etc.)
|
|
1551
|
-
this.ensureNoDataModelChanges(() => this.observeNonRuntimeMessage({
|
|
1552
|
-
message: messageCopy,
|
|
1553
|
-
local,
|
|
1554
|
-
isRuntimeMessage: false,
|
|
1555
|
-
savedOp,
|
|
1556
|
-
}));
|
|
1557
|
-
}
|
|
1598
|
+
this.processInboundMessages([{ message: messageCopy, localOpMetadata: undefined }], { batchStart: true, batchEnd: true }, // Single message
|
|
1599
|
+
local, savedOp, isRuntimeMessage(messageCopy) /* runtimeBatch */);
|
|
1600
|
+
}
|
|
1601
|
+
if (local) {
|
|
1602
|
+
// If we have processed a local op, this means that the container is
|
|
1603
|
+
// making progress and we can reset the counter for how many times
|
|
1604
|
+
// we have consecutively replayed the pending states
|
|
1605
|
+
this.resetReconnectCount();
|
|
1558
1606
|
}
|
|
1559
1607
|
}
|
|
1560
1608
|
/**
|
|
1561
|
-
* Processes
|
|
1562
|
-
*
|
|
1563
|
-
* @param
|
|
1609
|
+
* Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
|
|
1610
|
+
* @param messages - messages to process.
|
|
1611
|
+
* @param locationInBatch - Are we processing the start and/or end of a batch?
|
|
1612
|
+
* @param local - true if the messages were originally generated by the client receiving it.
|
|
1613
|
+
* @param savedOp - true if the message is a replayed saved op.
|
|
1614
|
+
* @param runtimeBatch - true if these are runtime messages.
|
|
1564
1615
|
*/
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
this.deltaManager.minimumSequenceNumber;
|
|
1573
|
-
}
|
|
1574
|
-
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1575
|
-
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1576
|
-
// messages once a batch has been fully processed.
|
|
1577
|
-
this.scheduleManager.beforeOpProcessing(message);
|
|
1578
|
-
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
1616
|
+
processInboundMessages(messages, locationInBatch, local, savedOp, runtimeBatch) {
|
|
1617
|
+
if (locationInBatch.batchStart) {
|
|
1618
|
+
const firstMessage = messages[0]?.message;
|
|
1619
|
+
assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
1620
|
+
this.scheduleManager.batchBegin(firstMessage);
|
|
1621
|
+
}
|
|
1622
|
+
let error;
|
|
1579
1623
|
try {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
}
|
|
1624
|
+
messages.forEach(({ message, localOpMetadata }) => {
|
|
1625
|
+
this.ensureNoDataModelChanges(() => {
|
|
1626
|
+
if (runtimeBatch) {
|
|
1627
|
+
this.validateAndProcessRuntimeMessage({
|
|
1628
|
+
message: message,
|
|
1629
|
+
local,
|
|
1630
|
+
savedOp,
|
|
1631
|
+
localOpMetadata,
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
this.observeNonRuntimeMessage(message);
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
});
|
|
1596
1639
|
}
|
|
1597
1640
|
catch (e) {
|
|
1598
|
-
|
|
1599
|
-
throw
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Process an empty batch, which will execute expected actions while processing even if there are no messages.
|
|
1604
|
-
* This is a separate function because the processCore function expects at least one message to process.
|
|
1605
|
-
* It is expected to happen only when the outbox produces an empty batch due to a resubmit flow.
|
|
1606
|
-
*/
|
|
1607
|
-
processEmptyBatch(emptyBatch, local) {
|
|
1608
|
-
const { emptyBatchSequenceNumber: sequenceNumber, batchStartCsn } = emptyBatch;
|
|
1609
|
-
assert(sequenceNumber !== undefined, 0x9fa /* emptyBatchSequenceNumber must be defined */);
|
|
1610
|
-
this.emit("batchBegin", { sequenceNumber });
|
|
1611
|
-
this._processedClientSequenceNumber = batchStartCsn;
|
|
1612
|
-
if (!this.hasPendingMessages()) {
|
|
1613
|
-
this.updateDocumentDirtyState(false);
|
|
1641
|
+
error = e;
|
|
1642
|
+
throw error;
|
|
1614
1643
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1644
|
+
finally {
|
|
1645
|
+
if (locationInBatch.batchEnd) {
|
|
1646
|
+
const lastMessage = messages[messages.length - 1]?.message;
|
|
1647
|
+
assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
|
|
1648
|
+
this.scheduleManager.batchEnd(error, lastMessage);
|
|
1649
|
+
}
|
|
1618
1650
|
}
|
|
1619
1651
|
}
|
|
1620
1652
|
/**
|
|
1621
1653
|
* Observes messages that are not intended for the runtime layer, updating/notifying Runtime systems as needed.
|
|
1622
|
-
* @param
|
|
1654
|
+
* @param message - non-runtime message to process.
|
|
1623
1655
|
*/
|
|
1624
|
-
observeNonRuntimeMessage(
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
if (this.deltaManager.minimumSequenceNumber <
|
|
1629
|
-
messageWithContext.message.minimumSequenceNumber) {
|
|
1630
|
-
messageWithContext.message.minimumSequenceNumber =
|
|
1631
|
-
this.deltaManager.minimumSequenceNumber;
|
|
1632
|
-
}
|
|
1633
|
-
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1634
|
-
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1635
|
-
// messages once a batch has been fully processed.
|
|
1636
|
-
this.scheduleManager.beforeOpProcessing(message);
|
|
1637
|
-
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
1638
|
-
try {
|
|
1639
|
-
// If there are no more pending messages after processing a local message,
|
|
1640
|
-
// the document is no longer dirty.
|
|
1641
|
-
if (!this.hasPendingMessages()) {
|
|
1642
|
-
this.updateDocumentDirtyState(false);
|
|
1643
|
-
}
|
|
1644
|
-
this.emit("op", message, messageWithContext.isRuntimeMessage);
|
|
1645
|
-
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1646
|
-
if (local) {
|
|
1647
|
-
// If we have processed a local op, this means that the container is
|
|
1648
|
-
// making progress and we can reset the counter for how many times
|
|
1649
|
-
// we have consecutively replayed the pending states
|
|
1650
|
-
this.resetReconnectCount();
|
|
1651
|
-
}
|
|
1656
|
+
observeNonRuntimeMessage(message) {
|
|
1657
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
1658
|
+
if (this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber) {
|
|
1659
|
+
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
1652
1660
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1661
|
+
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
1662
|
+
// If there are no more pending messages after processing a local message,
|
|
1663
|
+
// the document is no longer dirty.
|
|
1664
|
+
if (!this.hasPendingMessages()) {
|
|
1665
|
+
this.updateDocumentDirtyState(false);
|
|
1656
1666
|
}
|
|
1667
|
+
this.emit("op", message, false /* runtimeMessage */);
|
|
1657
1668
|
}
|
|
1658
1669
|
/**
|
|
1659
1670
|
* Assuming the given message is also a TypedContainerRuntimeMessage,
|
|
1660
1671
|
* checks its type and dispatches the message to the appropriate handler in the runtime.
|
|
1661
1672
|
* Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
|
|
1662
1673
|
*/
|
|
1663
|
-
validateAndProcessRuntimeMessage(messageWithContext
|
|
1664
|
-
const { local, message, savedOp } = messageWithContext;
|
|
1674
|
+
validateAndProcessRuntimeMessage(messageWithContext) {
|
|
1675
|
+
const { local, message, savedOp, localOpMetadata } = messageWithContext;
|
|
1676
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
1677
|
+
if (this.useDeltaManagerOpsProxy &&
|
|
1678
|
+
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber) {
|
|
1679
|
+
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
1680
|
+
}
|
|
1681
|
+
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
1682
|
+
// If there are no more pending messages after processing a local message,
|
|
1683
|
+
// the document is no longer dirty.
|
|
1684
|
+
if (!this.hasPendingMessages()) {
|
|
1685
|
+
this.updateDocumentDirtyState(false);
|
|
1686
|
+
}
|
|
1665
1687
|
switch (message.type) {
|
|
1666
1688
|
case ContainerMessageType.Attach:
|
|
1667
1689
|
case ContainerMessageType.Alias:
|
|
@@ -1722,20 +1744,25 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1722
1744
|
}
|
|
1723
1745
|
}
|
|
1724
1746
|
}
|
|
1747
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
1725
1748
|
}
|
|
1726
1749
|
/**
|
|
1727
1750
|
* Emits the Signal event and update the perf signal data.
|
|
1728
|
-
* @param clientSignalSequenceNumber - is the client signal sequence number to be uploaded.
|
|
1729
1751
|
*/
|
|
1730
|
-
sendSignalTelemetryEvent(
|
|
1731
|
-
const duration = Date.now() - this.
|
|
1752
|
+
sendSignalTelemetryEvent() {
|
|
1753
|
+
const duration = Date.now() - this._signalTracking.signalTimestamp;
|
|
1732
1754
|
this.mc.logger.sendPerformanceEvent({
|
|
1733
1755
|
eventName: "SignalLatency",
|
|
1734
|
-
duration,
|
|
1735
|
-
|
|
1756
|
+
duration, // Roundtrip duration of the tracked signal in milliseconds.
|
|
1757
|
+
signalsSent: this._signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
|
|
1758
|
+
signalsLost: this._signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
|
|
1759
|
+
outOfOrderSignals: this._signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
|
|
1760
|
+
reconnectCount: this.consecutiveReconnects, // Container reconnect count.
|
|
1736
1761
|
});
|
|
1737
|
-
this.
|
|
1738
|
-
this.
|
|
1762
|
+
this._signalTracking.signalsLost = 0;
|
|
1763
|
+
this._signalTracking.signalsOutOfOrder = 0;
|
|
1764
|
+
this._signalTracking.signalTimestamp = 0;
|
|
1765
|
+
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
1739
1766
|
}
|
|
1740
1767
|
processSignal(message, local) {
|
|
1741
1768
|
const envelope = message.content;
|
|
@@ -1743,29 +1770,55 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1743
1770
|
clientId: message.clientId,
|
|
1744
1771
|
content: envelope.contents.content,
|
|
1745
1772
|
type: envelope.contents.type,
|
|
1773
|
+
targetClientId: message.targetClientId,
|
|
1746
1774
|
};
|
|
1747
|
-
// Only collect signal telemetry for messages sent by the current client.
|
|
1748
|
-
if (message.clientId === this.clientId &&
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
this.
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1775
|
+
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
1776
|
+
if (message.clientId === this.clientId &&
|
|
1777
|
+
this.connected &&
|
|
1778
|
+
envelope.clientBroadcastSignalSequenceNumber !== undefined) {
|
|
1779
|
+
if (this._signalTracking.trackingSignalSequenceNumber !== undefined &&
|
|
1780
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber !== undefined) {
|
|
1781
|
+
if (envelope.clientBroadcastSignalSequenceNumber >=
|
|
1782
|
+
this._signalTracking.trackingSignalSequenceNumber) {
|
|
1783
|
+
// Calculate the number of signals lost and log the event.
|
|
1784
|
+
const signalsLost = envelope.clientBroadcastSignalSequenceNumber -
|
|
1785
|
+
this._signalTracking.trackingSignalSequenceNumber;
|
|
1786
|
+
if (signalsLost > 0) {
|
|
1787
|
+
this._signalTracking.signalsLost += signalsLost;
|
|
1788
|
+
this.mc.logger.sendErrorEvent({
|
|
1789
|
+
eventName: "SignalLost",
|
|
1790
|
+
signalsLost, // Number of lost signals detected.
|
|
1791
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
1792
|
+
clientBroadcastSignalSequenceNumber: envelope.clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
// Update the tracking signal sequence number to the next expected signal in the sequence.
|
|
1796
|
+
this._signalTracking.trackingSignalSequenceNumber =
|
|
1797
|
+
envelope.clientBroadcastSignalSequenceNumber + 1;
|
|
1798
|
+
}
|
|
1799
|
+
else if (envelope.clientBroadcastSignalSequenceNumber >=
|
|
1800
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber) {
|
|
1801
|
+
this._signalTracking.signalsOutOfOrder++;
|
|
1802
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1803
|
+
eventName: "SignalOutOfOrder",
|
|
1804
|
+
type: envelope.contents.type, // Type of signal that was received out of order.
|
|
1805
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
1806
|
+
clientBroadcastSignalSequenceNumber: envelope.clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
if (this._signalTracking.roundTripSignalSequenceNumber !== undefined &&
|
|
1810
|
+
envelope.clientBroadcastSignalSequenceNumber >=
|
|
1811
|
+
this._signalTracking.roundTripSignalSequenceNumber) {
|
|
1812
|
+
if (envelope.clientBroadcastSignalSequenceNumber ===
|
|
1813
|
+
this._signalTracking.roundTripSignalSequenceNumber) {
|
|
1814
|
+
// Latency tracked signal has been received.
|
|
1815
|
+
// We now log the roundtrip duration of the tracked signal.
|
|
1816
|
+
// This telemetry event also logs metrics for signals sent, signals lost, and out of order signals received.
|
|
1817
|
+
// These metrics are reset after logging the telemetry event.
|
|
1818
|
+
this.sendSignalTelemetryEvent();
|
|
1819
|
+
}
|
|
1820
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
1767
1821
|
}
|
|
1768
|
-
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1769
1822
|
}
|
|
1770
1823
|
}
|
|
1771
1824
|
if (envelope.address === undefined) {
|
|
@@ -1941,18 +1994,36 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1941
1994
|
}
|
|
1942
1995
|
return true;
|
|
1943
1996
|
}
|
|
1944
|
-
createNewSignalEnvelope(address, type, content) {
|
|
1945
|
-
const newSequenceNumber = ++this._perfSignalData.signalSequenceNumber;
|
|
1997
|
+
createNewSignalEnvelope(address, type, content, targetClientId) {
|
|
1946
1998
|
const newEnvelope = {
|
|
1947
1999
|
address,
|
|
1948
|
-
clientSignalSequenceNumber: newSequenceNumber,
|
|
1949
2000
|
contents: { type, content },
|
|
1950
2001
|
};
|
|
1951
|
-
|
|
1952
|
-
if (
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2002
|
+
const isBroadcastSignal = targetClientId === undefined;
|
|
2003
|
+
if (isBroadcastSignal) {
|
|
2004
|
+
const clientBroadcastSignalSequenceNumber = ++this._signalTracking
|
|
2005
|
+
.broadcastSignalSequenceNumber;
|
|
2006
|
+
newEnvelope.clientBroadcastSignalSequenceNumber = clientBroadcastSignalSequenceNumber;
|
|
2007
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement++;
|
|
2008
|
+
if (this._signalTracking.minimumTrackingSignalSequenceNumber === undefined ||
|
|
2009
|
+
this._signalTracking.trackingSignalSequenceNumber === undefined) {
|
|
2010
|
+
// Signal monitoring window is undefined
|
|
2011
|
+
// Initialize tracking to expect the next signal sent by the connected client.
|
|
2012
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber =
|
|
2013
|
+
clientBroadcastSignalSequenceNumber;
|
|
2014
|
+
this._signalTracking.trackingSignalSequenceNumber =
|
|
2015
|
+
clientBroadcastSignalSequenceNumber;
|
|
2016
|
+
}
|
|
2017
|
+
// We should not track the round trip of a new signal in the case we are already tracking one.
|
|
2018
|
+
if (clientBroadcastSignalSequenceNumber % this.defaultTelemetrySignalSampleCount === 1 &&
|
|
2019
|
+
this._signalTracking.roundTripSignalSequenceNumber === undefined) {
|
|
2020
|
+
this._signalTracking.signalTimestamp = Date.now();
|
|
2021
|
+
this._signalTracking.roundTripSignalSequenceNumber =
|
|
2022
|
+
clientBroadcastSignalSequenceNumber;
|
|
2023
|
+
this._signalTracking.totalSignalsSentInLatencyWindow +=
|
|
2024
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement;
|
|
2025
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
2026
|
+
}
|
|
1956
2027
|
}
|
|
1957
2028
|
return newEnvelope;
|
|
1958
2029
|
}
|
|
@@ -1961,10 +2032,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1961
2032
|
* @param type - Type of the signal.
|
|
1962
2033
|
* @param content - Content of the signal. Should be a JSON serializable object or primitive.
|
|
1963
2034
|
* @param targetClientId - When specified, the signal is only sent to the provided client id.
|
|
2035
|
+
*
|
|
2036
|
+
* @remarks
|
|
2037
|
+
*
|
|
2038
|
+
* The `targetClientId` parameter here is currently intended for internal testing purposes only.
|
|
2039
|
+
* Support for this option at container runtime is planned to be deprecated in the future.
|
|
2040
|
+
*
|
|
1964
2041
|
*/
|
|
1965
2042
|
submitSignal(type, content, targetClientId) {
|
|
1966
2043
|
this.verifyNotClosed();
|
|
1967
|
-
const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
|
|
2044
|
+
const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content, targetClientId);
|
|
1968
2045
|
return this.submitSignalFn(envelope, targetClientId);
|
|
1969
2046
|
}
|
|
1970
2047
|
setAttachState(attachState) {
|
|
@@ -2367,8 +2444,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2367
2444
|
// Counting dataStores and handles
|
|
2368
2445
|
// Because handles are unchanged dataStores in the current logic,
|
|
2369
2446
|
// summarized dataStore count is total dataStore count minus handle count
|
|
2370
|
-
// TODO why are we non null asserting here
|
|
2371
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2372
2447
|
const dataStoreTree = summaryTree.tree[channelsTreeName];
|
|
2373
2448
|
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
2374
2449
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
|
|
@@ -2691,6 +2766,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2691
2766
|
throw new Error("Runtime is closed");
|
|
2692
2767
|
}
|
|
2693
2768
|
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Resubmits each message in the batch, and then flushes the outbox.
|
|
2771
|
+
*
|
|
2772
|
+
* @remarks - If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
2773
|
+
* for correlation to detect container forking.
|
|
2774
|
+
*/
|
|
2694
2775
|
reSubmitBatch(batch, batchId) {
|
|
2695
2776
|
this.orderSequentially(() => {
|
|
2696
2777
|
for (const message of batch) {
|