@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/src/containerRuntime.ts
CHANGED
|
@@ -107,6 +107,7 @@ import {
|
|
|
107
107
|
ITelemetryLoggerExt,
|
|
108
108
|
DataCorruptionError,
|
|
109
109
|
DataProcessingError,
|
|
110
|
+
extractSafePropertiesFromMessage,
|
|
110
111
|
GenericError,
|
|
111
112
|
IEventSampler,
|
|
112
113
|
LoggingError,
|
|
@@ -162,7 +163,6 @@ import {
|
|
|
162
163
|
ContainerRuntimeGCMessage,
|
|
163
164
|
type ContainerRuntimeIdAllocationMessage,
|
|
164
165
|
type InboundSequencedContainerRuntimeMessage,
|
|
165
|
-
type InboundSequencedNonContainerRuntimeMessage,
|
|
166
166
|
type LocalContainerRuntimeMessage,
|
|
167
167
|
type OutboundContainerRuntimeMessage,
|
|
168
168
|
type UnknownContainerRuntimeMessage,
|
|
@@ -171,6 +171,8 @@ import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js";
|
|
|
171
171
|
import {
|
|
172
172
|
BatchId,
|
|
173
173
|
BatchMessage,
|
|
174
|
+
BatchStartInfo,
|
|
175
|
+
DuplicateBatchDetector,
|
|
174
176
|
ensureContentsDeserialized,
|
|
175
177
|
IBatch,
|
|
176
178
|
IBatchCheckpoint,
|
|
@@ -180,7 +182,6 @@ import {
|
|
|
180
182
|
OpSplitter,
|
|
181
183
|
Outbox,
|
|
182
184
|
RemoteMessageProcessor,
|
|
183
|
-
type InboundBatch,
|
|
184
185
|
} from "./opLifecycle/index.js";
|
|
185
186
|
import { pkgVersion } from "./packageVersion.js";
|
|
186
187
|
import {
|
|
@@ -688,25 +689,6 @@ export const makeLegacySendBatchFn =
|
|
|
688
689
|
return clientSequenceNumber;
|
|
689
690
|
};
|
|
690
691
|
|
|
691
|
-
/** Helper type for type constraints passed through several functions.
|
|
692
|
-
* local - Did this client send the op?
|
|
693
|
-
* savedOp - Is this op being replayed after being serialized (having been sequenced previously)
|
|
694
|
-
* localOpMetadata - Metadata maintained locally for a local op.
|
|
695
|
-
*/
|
|
696
|
-
type MessageWithContext = {
|
|
697
|
-
local: boolean;
|
|
698
|
-
savedOp?: boolean;
|
|
699
|
-
localOpMetadata?: unknown;
|
|
700
|
-
} & (
|
|
701
|
-
| {
|
|
702
|
-
message: InboundSequencedContainerRuntimeMessage;
|
|
703
|
-
isRuntimeMessage: true;
|
|
704
|
-
}
|
|
705
|
-
| {
|
|
706
|
-
message: InboundSequencedNonContainerRuntimeMessage;
|
|
707
|
-
isRuntimeMessage: false;
|
|
708
|
-
}
|
|
709
|
-
);
|
|
710
692
|
const summarizerRequestUrl = "_summarizer";
|
|
711
693
|
|
|
712
694
|
/**
|
|
@@ -767,17 +749,68 @@ function lastMessageFromMetadata(metadata: IContainerRuntimeMetadata | undefined
|
|
|
767
749
|
* We only want to log this once, to avoid spamming telemetry if we are wrong and these cases are hit commonly.
|
|
768
750
|
*/
|
|
769
751
|
let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: string) => {
|
|
770
|
-
// We only want to log this once per ContainerRuntime instance, to avoid spamming telemetry.
|
|
771
|
-
getSingleUseLegacyLogCallback = () => () => {};
|
|
772
|
-
|
|
773
752
|
return (codePath: string) => {
|
|
774
753
|
logger.sendTelemetryEvent({
|
|
775
754
|
eventName: "LegacyMessageFormat",
|
|
776
755
|
details: { codePath, type },
|
|
777
756
|
});
|
|
757
|
+
|
|
758
|
+
// Now that we've logged, prevent future logging (globally).
|
|
759
|
+
getSingleUseLegacyLogCallback = () => () => {};
|
|
778
760
|
};
|
|
779
761
|
};
|
|
780
762
|
|
|
763
|
+
/**
|
|
764
|
+
* This object holds the parameters necessary for the {@link loadContainerRuntime} function.
|
|
765
|
+
* @legacy
|
|
766
|
+
* @alpha
|
|
767
|
+
*/
|
|
768
|
+
export interface LoadContainerRuntimeParams {
|
|
769
|
+
/**
|
|
770
|
+
* Context of the container.
|
|
771
|
+
*/
|
|
772
|
+
context: IContainerContext;
|
|
773
|
+
/**
|
|
774
|
+
* Mapping from data store types to their corresponding factories
|
|
775
|
+
*/
|
|
776
|
+
registryEntries: NamedFluidDataStoreRegistryEntries;
|
|
777
|
+
/**
|
|
778
|
+
* Pass 'true' if loading from an existing snapshot.
|
|
779
|
+
*/
|
|
780
|
+
existing: boolean;
|
|
781
|
+
/**
|
|
782
|
+
* Additional options to be passed to the runtime
|
|
783
|
+
*/
|
|
784
|
+
runtimeOptions?: IContainerRuntimeOptions;
|
|
785
|
+
/**
|
|
786
|
+
* runtime services provided with context
|
|
787
|
+
*/
|
|
788
|
+
containerScope?: FluidObject;
|
|
789
|
+
/**
|
|
790
|
+
* Promise that resolves to an object which will act as entryPoint for the Container.
|
|
791
|
+
*/
|
|
792
|
+
provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>;
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Request handler for the request() method of the container runtime.
|
|
796
|
+
* Only relevant for back-compat while we remove the request() method and move fully to entryPoint as the main pattern.
|
|
797
|
+
* @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
|
|
798
|
+
* */
|
|
799
|
+
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
|
|
803
|
+
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
|
|
804
|
+
* @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.
|
|
805
|
+
* @legacy
|
|
806
|
+
* @alpha
|
|
807
|
+
*/
|
|
808
|
+
export async function loadContainerRuntime(
|
|
809
|
+
params: LoadContainerRuntimeParams,
|
|
810
|
+
): Promise<IContainerRuntime & IRuntime> {
|
|
811
|
+
return ContainerRuntime.loadRuntime(params);
|
|
812
|
+
}
|
|
813
|
+
|
|
781
814
|
/**
|
|
782
815
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
783
816
|
* It will define the store level mappings.
|
|
@@ -1285,13 +1318,19 @@ export class ContainerRuntime
|
|
|
1285
1318
|
private dirtyContainer: boolean;
|
|
1286
1319
|
private emitDirtyDocumentEvent = true;
|
|
1287
1320
|
private readonly disableAttachReorder: boolean | undefined;
|
|
1321
|
+
private readonly useDeltaManagerOpsProxy: boolean;
|
|
1288
1322
|
private readonly closeSummarizerDelayMs: number;
|
|
1289
1323
|
private readonly defaultTelemetrySignalSampleCount = 100;
|
|
1290
|
-
private readonly
|
|
1324
|
+
private readonly _signalTracking: IPerfSignalReport = {
|
|
1325
|
+
totalSignalsSentInLatencyWindow: 0,
|
|
1291
1326
|
signalsLost: 0,
|
|
1292
|
-
|
|
1327
|
+
signalsOutOfOrder: 0,
|
|
1328
|
+
signalsSentSinceLastLatencyMeasurement: 0,
|
|
1329
|
+
broadcastSignalSequenceNumber: 0,
|
|
1293
1330
|
signalTimestamp: 0,
|
|
1331
|
+
roundTripSignalSequenceNumber: undefined,
|
|
1294
1332
|
trackingSignalSequenceNumber: undefined,
|
|
1333
|
+
minimumTrackingSignalSequenceNumber: undefined,
|
|
1295
1334
|
};
|
|
1296
1335
|
|
|
1297
1336
|
/**
|
|
@@ -1303,6 +1342,7 @@ export class ContainerRuntime
|
|
|
1303
1342
|
private readonly scheduleManager: ScheduleManager;
|
|
1304
1343
|
private readonly blobManager: BlobManager;
|
|
1305
1344
|
private readonly pendingStateManager: PendingStateManager;
|
|
1345
|
+
private readonly duplicateBatchDetector: DuplicateBatchDetector | undefined;
|
|
1306
1346
|
private readonly outbox: Outbox;
|
|
1307
1347
|
private readonly garbageCollector: IGarbageCollector;
|
|
1308
1348
|
|
|
@@ -1583,8 +1623,8 @@ export class ContainerRuntime
|
|
|
1583
1623
|
);
|
|
1584
1624
|
|
|
1585
1625
|
let outerDeltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
|
|
1586
|
-
|
|
1587
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy")
|
|
1626
|
+
this.useDeltaManagerOpsProxy =
|
|
1627
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true;
|
|
1588
1628
|
// The summarizerDeltaManager Proxy is used to lie to the summarizer to convince it is in the right state as a summarizer client.
|
|
1589
1629
|
const summarizerDeltaManagerProxy = new DeltaManagerSummarizerProxy(
|
|
1590
1630
|
this.innerDeltaManager,
|
|
@@ -1593,7 +1633,7 @@ export class ContainerRuntime
|
|
|
1593
1633
|
|
|
1594
1634
|
// The DeltaManagerPendingOpsProxy is used to control the minimum sequence number
|
|
1595
1635
|
// It allows us to lie to the layers below so that they can maintain enough local state for rebasing ops.
|
|
1596
|
-
if (useDeltaManagerOpsProxy) {
|
|
1636
|
+
if (this.useDeltaManagerOpsProxy) {
|
|
1597
1637
|
const pendingOpsDeltaManagerProxy = new DeltaManagerPendingOpsProxy(
|
|
1598
1638
|
summarizerDeltaManagerProxy,
|
|
1599
1639
|
this.pendingStateManager,
|
|
@@ -1636,6 +1676,13 @@ export class ContainerRuntime
|
|
|
1636
1676
|
throw error;
|
|
1637
1677
|
}
|
|
1638
1678
|
|
|
1679
|
+
// DuplicateBatchDetection is only enabled if Offline Load is enabled
|
|
1680
|
+
// It maintains a cache of all batchIds/sequenceNumbers within the collab window.
|
|
1681
|
+
// Don't waste resources doing so if not needed.
|
|
1682
|
+
if (this.offlineEnabled) {
|
|
1683
|
+
this.duplicateBatchDetector = new DuplicateBatchDetector();
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1639
1686
|
if (context.attachState === AttachState.Attached) {
|
|
1640
1687
|
const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
|
|
1641
1688
|
if (
|
|
@@ -2199,13 +2246,9 @@ export class ContainerRuntime
|
|
|
2199
2246
|
let childTree = snapshotTree;
|
|
2200
2247
|
for (const part of pathParts) {
|
|
2201
2248
|
if (hasIsolatedChannels) {
|
|
2202
|
-
|
|
2203
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2204
|
-
childTree = childTree.trees[channelsTreeName]!;
|
|
2249
|
+
childTree = childTree?.trees[channelsTreeName];
|
|
2205
2250
|
}
|
|
2206
|
-
|
|
2207
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2208
|
-
childTree = childTree.trees[part]!;
|
|
2251
|
+
childTree = childTree?.trees[part];
|
|
2209
2252
|
}
|
|
2210
2253
|
return childTree;
|
|
2211
2254
|
}
|
|
@@ -2257,9 +2300,7 @@ export class ContainerRuntime
|
|
|
2257
2300
|
}
|
|
2258
2301
|
|
|
2259
2302
|
if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
|
|
2260
|
-
|
|
2261
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2262
|
-
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]!);
|
|
2303
|
+
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
2263
2304
|
return blob
|
|
2264
2305
|
? {
|
|
2265
2306
|
status: 200,
|
|
@@ -2597,9 +2638,14 @@ export class ContainerRuntime
|
|
|
2597
2638
|
this._connected = connected;
|
|
2598
2639
|
|
|
2599
2640
|
if (!connected) {
|
|
2600
|
-
this.
|
|
2601
|
-
this.
|
|
2602
|
-
this.
|
|
2641
|
+
this._signalTracking.signalsLost = 0;
|
|
2642
|
+
this._signalTracking.signalsOutOfOrder = 0;
|
|
2643
|
+
this._signalTracking.signalTimestamp = 0;
|
|
2644
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
2645
|
+
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
2646
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
2647
|
+
this._signalTracking.trackingSignalSequenceNumber = undefined;
|
|
2648
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
|
|
2603
2649
|
} else {
|
|
2604
2650
|
assert(
|
|
2605
2651
|
this.attachState === AttachState.Attached,
|
|
@@ -2665,197 +2711,220 @@ export class ContainerRuntime
|
|
|
2665
2711
|
if (hasModernRuntimeMessageEnvelope) {
|
|
2666
2712
|
// If the message has the modern message envelope, then process it here.
|
|
2667
2713
|
// Here we unpack the message (decompress, unchunk, and/or ungroup) into a batch of messages with ContainerMessageType
|
|
2668
|
-
const
|
|
2669
|
-
if (
|
|
2714
|
+
const inboundResult = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
|
|
2715
|
+
if (inboundResult === undefined) {
|
|
2670
2716
|
// This means the incoming message is an incomplete part of a message or batch
|
|
2671
2717
|
// and we need to process more messages before the rest of the system can understand it.
|
|
2672
2718
|
return;
|
|
2673
2719
|
}
|
|
2674
2720
|
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2721
|
+
if ("batchStart" in inboundResult) {
|
|
2722
|
+
const batchStart: BatchStartInfo = inboundResult.batchStart;
|
|
2723
|
+
const result = this.duplicateBatchDetector?.processInboundBatch(batchStart);
|
|
2724
|
+
if (result?.duplicate) {
|
|
2725
|
+
const error = new DataCorruptionError(
|
|
2726
|
+
"Duplicate batch - The same batch was sequenced twice",
|
|
2727
|
+
{ batchId: batchStart.batchId },
|
|
2728
|
+
);
|
|
2729
|
+
|
|
2730
|
+
this.mc.logger.sendTelemetryEvent(
|
|
2731
|
+
{
|
|
2732
|
+
eventName: "DuplicateBatch",
|
|
2733
|
+
details: {
|
|
2734
|
+
batchId: batchStart.batchId,
|
|
2735
|
+
clientId: batchStart.clientId,
|
|
2736
|
+
batchStartCsn: batchStart.batchStartCsn,
|
|
2737
|
+
size: inboundResult.length,
|
|
2738
|
+
duplicateBatchSequenceNumber: result.otherSequenceNumber,
|
|
2739
|
+
...extractSafePropertiesFromMessage(batchStart.keyMessage),
|
|
2740
|
+
},
|
|
2741
|
+
},
|
|
2742
|
+
error,
|
|
2743
|
+
);
|
|
2744
|
+
throw error;
|
|
2745
|
+
}
|
|
2693
2746
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
//
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2747
|
+
|
|
2748
|
+
let runtimeBatch: boolean = true;
|
|
2749
|
+
// Reach out to PendingStateManager, either to zip localOpMetadata into the *local* message list,
|
|
2750
|
+
// or to check to ensure the *remote* messages don't match the batchId of a pending local batch.
|
|
2751
|
+
// This latter case would indicate that the container has forked - two copies are trying to persist the same local changes.
|
|
2752
|
+
let messagesWithPendingState: {
|
|
2753
|
+
message: ISequencedDocumentMessage;
|
|
2754
|
+
localOpMetadata?: unknown;
|
|
2755
|
+
}[] = this.pendingStateManager.processInboundMessages(inboundResult, local);
|
|
2756
|
+
|
|
2757
|
+
if (inboundResult.type !== "fullBatch") {
|
|
2758
|
+
assert(
|
|
2759
|
+
messagesWithPendingState.length === 1,
|
|
2760
|
+
0xa3d /* Partial batch should have exactly one message */,
|
|
2706
2761
|
);
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
isRuntimeMessage: false,
|
|
2714
|
-
savedOp,
|
|
2715
|
-
}),
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
if (messagesWithPendingState.length === 0) {
|
|
2765
|
+
assert(
|
|
2766
|
+
inboundResult.type === "fullBatch",
|
|
2767
|
+
0xa3e /* Empty batch is always considered a full batch */,
|
|
2716
2768
|
);
|
|
2769
|
+
/**
|
|
2770
|
+
* We need to process an empty batch, which will execute expected actions while processing even if there
|
|
2771
|
+
* are no inner runtime messages.
|
|
2772
|
+
*
|
|
2773
|
+
* Empty batches are produced by the outbox on resubmit when the resubmit flow resulted in no runtime
|
|
2774
|
+
* messages.
|
|
2775
|
+
* This can happen if changes from a remote client "cancel out" the pending changes being resubmitted by
|
|
2776
|
+
* this client. We submit an empty batch if "offline load" (aka rehydrating from stashed state) is
|
|
2777
|
+
* enabled, to ensure we account for this batch when comparing batchIds, checking for a forked container.
|
|
2778
|
+
* Otherwise, we would not realize this container has forked in the case where it did fork, and a batch
|
|
2779
|
+
* became empty but wasn't submitted as such.
|
|
2780
|
+
*/
|
|
2781
|
+
messagesWithPendingState = [
|
|
2782
|
+
{
|
|
2783
|
+
message: inboundResult.batchStart.keyMessage,
|
|
2784
|
+
localOpMetadata: undefined,
|
|
2785
|
+
},
|
|
2786
|
+
];
|
|
2787
|
+
// Empty batch message is a non-runtime message as it was generated by the op grouping manager.
|
|
2788
|
+
runtimeBatch = false;
|
|
2717
2789
|
}
|
|
2790
|
+
|
|
2791
|
+
const locationInBatch: { batchStart: boolean; batchEnd: boolean } =
|
|
2792
|
+
inboundResult.type === "fullBatch"
|
|
2793
|
+
? { batchStart: true, batchEnd: true }
|
|
2794
|
+
: inboundResult.type === "batchStartingMessage"
|
|
2795
|
+
? { batchStart: true, batchEnd: false }
|
|
2796
|
+
: { batchStart: false, batchEnd: inboundResult.batchEnd === true };
|
|
2797
|
+
|
|
2798
|
+
this.processInboundMessages(
|
|
2799
|
+
messagesWithPendingState,
|
|
2800
|
+
locationInBatch,
|
|
2801
|
+
local,
|
|
2802
|
+
savedOp,
|
|
2803
|
+
runtimeBatch,
|
|
2804
|
+
);
|
|
2805
|
+
} else {
|
|
2806
|
+
this.processInboundMessages(
|
|
2807
|
+
[{ message: messageCopy, localOpMetadata: undefined }],
|
|
2808
|
+
{ batchStart: true, batchEnd: true }, // Single message
|
|
2809
|
+
local,
|
|
2810
|
+
savedOp,
|
|
2811
|
+
isRuntimeMessage(messageCopy) /* runtimeBatch */,
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
if (local) {
|
|
2816
|
+
// If we have processed a local op, this means that the container is
|
|
2817
|
+
// making progress and we can reset the counter for how many times
|
|
2818
|
+
// we have consecutively replayed the pending states
|
|
2819
|
+
this.resetReconnectCount();
|
|
2718
2820
|
}
|
|
2719
2821
|
}
|
|
2720
2822
|
|
|
2721
2823
|
private _processedClientSequenceNumber: number | undefined;
|
|
2722
2824
|
|
|
2723
2825
|
/**
|
|
2724
|
-
* Processes
|
|
2725
|
-
*
|
|
2726
|
-
* @param
|
|
2826
|
+
* Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
|
|
2827
|
+
* @param messages - messages to process.
|
|
2828
|
+
* @param locationInBatch - Are we processing the start and/or end of a batch?
|
|
2829
|
+
* @param local - true if the messages were originally generated by the client receiving it.
|
|
2830
|
+
* @param savedOp - true if the message is a replayed saved op.
|
|
2831
|
+
* @param runtimeBatch - true if these are runtime messages.
|
|
2727
2832
|
*/
|
|
2728
|
-
private
|
|
2729
|
-
|
|
2833
|
+
private processInboundMessages(
|
|
2834
|
+
messages: {
|
|
2835
|
+
message: ISequencedDocumentMessage;
|
|
2836
|
+
localOpMetadata?: unknown;
|
|
2837
|
+
}[],
|
|
2838
|
+
locationInBatch: { batchStart: boolean; batchEnd: boolean },
|
|
2839
|
+
local: boolean,
|
|
2840
|
+
savedOp: boolean | undefined,
|
|
2841
|
+
runtimeBatch: boolean,
|
|
2730
2842
|
) {
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
if (
|
|
2736
|
-
this.deltaManager.minimumSequenceNumber <
|
|
2737
|
-
messageWithContext.message.minimumSequenceNumber
|
|
2738
|
-
) {
|
|
2739
|
-
messageWithContext.message.minimumSequenceNumber =
|
|
2740
|
-
this.deltaManager.minimumSequenceNumber;
|
|
2843
|
+
if (locationInBatch.batchStart) {
|
|
2844
|
+
const firstMessage = messages[0]?.message;
|
|
2845
|
+
assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
2846
|
+
this.scheduleManager.batchBegin(firstMessage);
|
|
2741
2847
|
}
|
|
2742
2848
|
|
|
2743
|
-
|
|
2744
|
-
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
2745
|
-
// messages once a batch has been fully processed.
|
|
2746
|
-
this.scheduleManager.beforeOpProcessing(message);
|
|
2747
|
-
|
|
2748
|
-
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2749
|
-
|
|
2849
|
+
let error: unknown;
|
|
2750
2850
|
try {
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
this.emit("op", message, messageWithContext.isRuntimeMessage);
|
|
2766
|
-
|
|
2767
|
-
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
2768
|
-
|
|
2769
|
-
if (local) {
|
|
2770
|
-
// If we have processed a local op, this means that the container is
|
|
2771
|
-
// making progress and we can reset the counter for how many times
|
|
2772
|
-
// we have consecutively replayed the pending states
|
|
2773
|
-
this.resetReconnectCount();
|
|
2774
|
-
}
|
|
2851
|
+
messages.forEach(({ message, localOpMetadata }) => {
|
|
2852
|
+
this.ensureNoDataModelChanges(() => {
|
|
2853
|
+
if (runtimeBatch) {
|
|
2854
|
+
this.validateAndProcessRuntimeMessage({
|
|
2855
|
+
message: message as InboundSequencedContainerRuntimeMessage,
|
|
2856
|
+
local,
|
|
2857
|
+
savedOp,
|
|
2858
|
+
localOpMetadata,
|
|
2859
|
+
});
|
|
2860
|
+
} else {
|
|
2861
|
+
this.observeNonRuntimeMessage(message);
|
|
2862
|
+
}
|
|
2863
|
+
});
|
|
2864
|
+
});
|
|
2775
2865
|
} catch (e) {
|
|
2776
|
-
|
|
2777
|
-
throw
|
|
2866
|
+
error = e;
|
|
2867
|
+
throw error;
|
|
2868
|
+
} finally {
|
|
2869
|
+
if (locationInBatch.batchEnd) {
|
|
2870
|
+
const lastMessage = messages[messages.length - 1]?.message;
|
|
2871
|
+
assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
|
|
2872
|
+
this.scheduleManager.batchEnd(error, lastMessage);
|
|
2873
|
+
}
|
|
2778
2874
|
}
|
|
2779
2875
|
}
|
|
2780
2876
|
|
|
2781
2877
|
/**
|
|
2782
|
-
*
|
|
2783
|
-
*
|
|
2784
|
-
* It is expected to happen only when the outbox produces an empty batch due to a resubmit flow.
|
|
2878
|
+
* Observes messages that are not intended for the runtime layer, updating/notifying Runtime systems as needed.
|
|
2879
|
+
* @param message - non-runtime message to process.
|
|
2785
2880
|
*/
|
|
2786
|
-
private
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2881
|
+
private observeNonRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
2882
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2883
|
+
if (this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber) {
|
|
2884
|
+
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2888
|
+
|
|
2889
|
+
// If there are no more pending messages after processing a local message,
|
|
2890
|
+
// the document is no longer dirty.
|
|
2791
2891
|
if (!this.hasPendingMessages()) {
|
|
2792
2892
|
this.updateDocumentDirtyState(false);
|
|
2793
2893
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
this.resetReconnectCount();
|
|
2797
|
-
}
|
|
2894
|
+
|
|
2895
|
+
this.emit("op", message, false /* runtimeMessage */);
|
|
2798
2896
|
}
|
|
2799
2897
|
|
|
2800
2898
|
/**
|
|
2801
|
-
*
|
|
2802
|
-
*
|
|
2899
|
+
* Assuming the given message is also a TypedContainerRuntimeMessage,
|
|
2900
|
+
* checks its type and dispatches the message to the appropriate handler in the runtime.
|
|
2901
|
+
* Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
|
|
2803
2902
|
*/
|
|
2804
|
-
private observeNonRuntimeMessage(
|
|
2805
|
-
messageWithContext: MessageWithContext & { isRuntimeMessage: false },
|
|
2806
|
-
) {
|
|
2807
|
-
const { message, local } = messageWithContext;
|
|
2808
2903
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2904
|
+
private validateAndProcessRuntimeMessage(messageWithContext: {
|
|
2905
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
2906
|
+
local: boolean;
|
|
2907
|
+
savedOp?: boolean;
|
|
2908
|
+
localOpMetadata?: unknown;
|
|
2909
|
+
}): void {
|
|
2910
|
+
const { local, message, savedOp, localOpMetadata } = messageWithContext;
|
|
2911
|
+
|
|
2912
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2811
2913
|
if (
|
|
2812
|
-
this.
|
|
2813
|
-
|
|
2914
|
+
this.useDeltaManagerOpsProxy &&
|
|
2915
|
+
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
|
|
2814
2916
|
) {
|
|
2815
|
-
|
|
2816
|
-
this.deltaManager.minimumSequenceNumber;
|
|
2917
|
+
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
2817
2918
|
}
|
|
2818
2919
|
|
|
2819
|
-
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
2820
|
-
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
2821
|
-
// messages once a batch has been fully processed.
|
|
2822
|
-
this.scheduleManager.beforeOpProcessing(message);
|
|
2823
|
-
|
|
2824
2920
|
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2825
2921
|
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
this.updateDocumentDirtyState(false);
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
this.emit("op", message, messageWithContext.isRuntimeMessage);
|
|
2834
|
-
|
|
2835
|
-
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
2836
|
-
|
|
2837
|
-
if (local) {
|
|
2838
|
-
// If we have processed a local op, this means that the container is
|
|
2839
|
-
// making progress and we can reset the counter for how many times
|
|
2840
|
-
// we have consecutively replayed the pending states
|
|
2841
|
-
this.resetReconnectCount();
|
|
2842
|
-
}
|
|
2843
|
-
} catch (e) {
|
|
2844
|
-
this.scheduleManager.afterOpProcessing(e, message);
|
|
2845
|
-
throw e;
|
|
2922
|
+
// If there are no more pending messages after processing a local message,
|
|
2923
|
+
// the document is no longer dirty.
|
|
2924
|
+
if (!this.hasPendingMessages()) {
|
|
2925
|
+
this.updateDocumentDirtyState(false);
|
|
2846
2926
|
}
|
|
2847
|
-
}
|
|
2848
2927
|
|
|
2849
|
-
/**
|
|
2850
|
-
* Assuming the given message is also a TypedContainerRuntimeMessage,
|
|
2851
|
-
* checks its type and dispatches the message to the appropriate handler in the runtime.
|
|
2852
|
-
* Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
|
|
2853
|
-
*/
|
|
2854
|
-
private validateAndProcessRuntimeMessage(
|
|
2855
|
-
messageWithContext: MessageWithContext & { isRuntimeMessage: true },
|
|
2856
|
-
localOpMetadata: unknown,
|
|
2857
|
-
): void {
|
|
2858
|
-
const { local, message, savedOp } = messageWithContext;
|
|
2859
2928
|
switch (message.type) {
|
|
2860
2929
|
case ContainerMessageType.Attach:
|
|
2861
2930
|
case ContainerMessageType.Alias:
|
|
@@ -2929,22 +2998,27 @@ export class ContainerRuntime
|
|
|
2929
2998
|
}
|
|
2930
2999
|
}
|
|
2931
3000
|
}
|
|
3001
|
+
|
|
3002
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2932
3003
|
}
|
|
2933
3004
|
|
|
2934
3005
|
/**
|
|
2935
3006
|
* Emits the Signal event and update the perf signal data.
|
|
2936
|
-
* @param clientSignalSequenceNumber - is the client signal sequence number to be uploaded.
|
|
2937
3007
|
*/
|
|
2938
|
-
private sendSignalTelemetryEvent(
|
|
2939
|
-
const duration = Date.now() - this.
|
|
3008
|
+
private sendSignalTelemetryEvent() {
|
|
3009
|
+
const duration = Date.now() - this._signalTracking.signalTimestamp;
|
|
2940
3010
|
this.mc.logger.sendPerformanceEvent({
|
|
2941
3011
|
eventName: "SignalLatency",
|
|
2942
|
-
duration,
|
|
2943
|
-
|
|
3012
|
+
duration, // Roundtrip duration of the tracked signal in milliseconds.
|
|
3013
|
+
signalsSent: this._signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
|
|
3014
|
+
signalsLost: this._signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
|
|
3015
|
+
outOfOrderSignals: this._signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
|
|
3016
|
+
reconnectCount: this.consecutiveReconnects, // Container reconnect count.
|
|
2944
3017
|
});
|
|
2945
|
-
|
|
2946
|
-
this.
|
|
2947
|
-
this.
|
|
3018
|
+
this._signalTracking.signalsLost = 0;
|
|
3019
|
+
this._signalTracking.signalsOutOfOrder = 0;
|
|
3020
|
+
this._signalTracking.signalTimestamp = 0;
|
|
3021
|
+
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
2948
3022
|
}
|
|
2949
3023
|
|
|
2950
3024
|
public processSignal(message: ISignalMessage, local: boolean) {
|
|
@@ -2953,33 +3027,69 @@ export class ContainerRuntime
|
|
|
2953
3027
|
clientId: message.clientId,
|
|
2954
3028
|
content: envelope.contents.content,
|
|
2955
3029
|
type: envelope.contents.type,
|
|
3030
|
+
targetClientId: message.targetClientId,
|
|
2956
3031
|
};
|
|
2957
3032
|
|
|
2958
|
-
// Only collect signal telemetry for messages sent by the current client.
|
|
2959
|
-
if (
|
|
2960
|
-
|
|
3033
|
+
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
3034
|
+
if (
|
|
3035
|
+
message.clientId === this.clientId &&
|
|
3036
|
+
this.connected &&
|
|
3037
|
+
envelope.clientBroadcastSignalSequenceNumber !== undefined
|
|
3038
|
+
) {
|
|
2961
3039
|
if (
|
|
2962
|
-
this.
|
|
2963
|
-
|
|
3040
|
+
this._signalTracking.trackingSignalSequenceNumber !== undefined &&
|
|
3041
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber !== undefined
|
|
2964
3042
|
) {
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
signalsLost
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3043
|
+
if (
|
|
3044
|
+
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3045
|
+
this._signalTracking.trackingSignalSequenceNumber
|
|
3046
|
+
) {
|
|
3047
|
+
// Calculate the number of signals lost and log the event.
|
|
3048
|
+
const signalsLost =
|
|
3049
|
+
envelope.clientBroadcastSignalSequenceNumber -
|
|
3050
|
+
this._signalTracking.trackingSignalSequenceNumber;
|
|
3051
|
+
if (signalsLost > 0) {
|
|
3052
|
+
this._signalTracking.signalsLost += signalsLost;
|
|
3053
|
+
this.mc.logger.sendErrorEvent({
|
|
3054
|
+
eventName: "SignalLost",
|
|
3055
|
+
signalsLost, // Number of lost signals detected.
|
|
3056
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3057
|
+
clientBroadcastSignalSequenceNumber:
|
|
3058
|
+
envelope.clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
// Update the tracking signal sequence number to the next expected signal in the sequence.
|
|
3062
|
+
this._signalTracking.trackingSignalSequenceNumber =
|
|
3063
|
+
envelope.clientBroadcastSignalSequenceNumber + 1;
|
|
3064
|
+
} else if (
|
|
3065
|
+
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3066
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber
|
|
3067
|
+
) {
|
|
3068
|
+
this._signalTracking.signalsOutOfOrder++;
|
|
3069
|
+
this.mc.logger.sendTelemetryEvent({
|
|
3070
|
+
eventName: "SignalOutOfOrder",
|
|
3071
|
+
type: envelope.contents.type, // Type of signal that was received out of order.
|
|
3072
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3073
|
+
clientBroadcastSignalSequenceNumber: envelope.clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
if (
|
|
3077
|
+
this._signalTracking.roundTripSignalSequenceNumber !== undefined &&
|
|
3078
|
+
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3079
|
+
this._signalTracking.roundTripSignalSequenceNumber
|
|
3080
|
+
) {
|
|
3081
|
+
if (
|
|
3082
|
+
envelope.clientBroadcastSignalSequenceNumber ===
|
|
3083
|
+
this._signalTracking.roundTripSignalSequenceNumber
|
|
3084
|
+
) {
|
|
3085
|
+
// Latency tracked signal has been received.
|
|
3086
|
+
// We now log the roundtrip duration of the tracked signal.
|
|
3087
|
+
// This telemetry event also logs metrics for signals sent, signals lost, and out of order signals received.
|
|
3088
|
+
// These metrics are reset after logging the telemetry event.
|
|
3089
|
+
this.sendSignalTelemetryEvent();
|
|
3090
|
+
}
|
|
3091
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
2981
3092
|
}
|
|
2982
|
-
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
2983
3093
|
}
|
|
2984
3094
|
}
|
|
2985
3095
|
|
|
@@ -3227,21 +3337,45 @@ export class ContainerRuntime
|
|
|
3227
3337
|
address: string | undefined,
|
|
3228
3338
|
type: string,
|
|
3229
3339
|
content: any,
|
|
3340
|
+
targetClientId?: string,
|
|
3230
3341
|
): ISignalEnvelope {
|
|
3231
|
-
const newSequenceNumber = ++this._perfSignalData.signalSequenceNumber;
|
|
3232
3342
|
const newEnvelope: ISignalEnvelope = {
|
|
3233
3343
|
address,
|
|
3234
|
-
clientSignalSequenceNumber: newSequenceNumber,
|
|
3235
3344
|
contents: { type, content },
|
|
3236
3345
|
};
|
|
3237
3346
|
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
this.
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
this.
|
|
3347
|
+
const isBroadcastSignal = targetClientId === undefined;
|
|
3348
|
+
|
|
3349
|
+
if (isBroadcastSignal) {
|
|
3350
|
+
const clientBroadcastSignalSequenceNumber = ++this._signalTracking
|
|
3351
|
+
.broadcastSignalSequenceNumber;
|
|
3352
|
+
newEnvelope.clientBroadcastSignalSequenceNumber = clientBroadcastSignalSequenceNumber;
|
|
3353
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement++;
|
|
3354
|
+
|
|
3355
|
+
if (
|
|
3356
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber === undefined ||
|
|
3357
|
+
this._signalTracking.trackingSignalSequenceNumber === undefined
|
|
3358
|
+
) {
|
|
3359
|
+
// Signal monitoring window is undefined
|
|
3360
|
+
// Initialize tracking to expect the next signal sent by the connected client.
|
|
3361
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber =
|
|
3362
|
+
clientBroadcastSignalSequenceNumber;
|
|
3363
|
+
this._signalTracking.trackingSignalSequenceNumber =
|
|
3364
|
+
clientBroadcastSignalSequenceNumber;
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// We should not track the round trip of a new signal in the case we are already tracking one.
|
|
3368
|
+
if (
|
|
3369
|
+
clientBroadcastSignalSequenceNumber % this.defaultTelemetrySignalSampleCount === 1 &&
|
|
3370
|
+
this._signalTracking.roundTripSignalSequenceNumber === undefined
|
|
3371
|
+
) {
|
|
3372
|
+
this._signalTracking.signalTimestamp = Date.now();
|
|
3373
|
+
this._signalTracking.roundTripSignalSequenceNumber =
|
|
3374
|
+
clientBroadcastSignalSequenceNumber;
|
|
3375
|
+
this._signalTracking.totalSignalsSentInLatencyWindow +=
|
|
3376
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement;
|
|
3377
|
+
this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
|
|
3378
|
+
}
|
|
3245
3379
|
}
|
|
3246
3380
|
|
|
3247
3381
|
return newEnvelope;
|
|
@@ -3252,10 +3386,21 @@ export class ContainerRuntime
|
|
|
3252
3386
|
* @param type - Type of the signal.
|
|
3253
3387
|
* @param content - Content of the signal. Should be a JSON serializable object or primitive.
|
|
3254
3388
|
* @param targetClientId - When specified, the signal is only sent to the provided client id.
|
|
3389
|
+
*
|
|
3390
|
+
* @remarks
|
|
3391
|
+
*
|
|
3392
|
+
* The `targetClientId` parameter here is currently intended for internal testing purposes only.
|
|
3393
|
+
* Support for this option at container runtime is planned to be deprecated in the future.
|
|
3394
|
+
*
|
|
3255
3395
|
*/
|
|
3256
3396
|
public submitSignal(type: string, content: unknown, targetClientId?: string) {
|
|
3257
3397
|
this.verifyNotClosed();
|
|
3258
|
-
const envelope = this.createNewSignalEnvelope(
|
|
3398
|
+
const envelope = this.createNewSignalEnvelope(
|
|
3399
|
+
undefined /* address */,
|
|
3400
|
+
type,
|
|
3401
|
+
content,
|
|
3402
|
+
targetClientId,
|
|
3403
|
+
);
|
|
3259
3404
|
return this.submitSignalFn(envelope, targetClientId);
|
|
3260
3405
|
}
|
|
3261
3406
|
|
|
@@ -3812,9 +3957,7 @@ export class ContainerRuntime
|
|
|
3812
3957
|
// Counting dataStores and handles
|
|
3813
3958
|
// Because handles are unchanged dataStores in the current logic,
|
|
3814
3959
|
// summarized dataStore count is total dataStore count minus handle count
|
|
3815
|
-
|
|
3816
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
3817
|
-
const dataStoreTree = summaryTree.tree[channelsTreeName]!;
|
|
3960
|
+
const dataStoreTree = summaryTree.tree[channelsTreeName];
|
|
3818
3961
|
|
|
3819
3962
|
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
3820
3963
|
const handleCount = Object.values(dataStoreTree.tree).filter(
|
|
@@ -4226,6 +4369,12 @@ export class ContainerRuntime
|
|
|
4226
4369
|
}
|
|
4227
4370
|
}
|
|
4228
4371
|
|
|
4372
|
+
/**
|
|
4373
|
+
* Resubmits each message in the batch, and then flushes the outbox.
|
|
4374
|
+
*
|
|
4375
|
+
* @remarks - If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
4376
|
+
* for correlation to detect container forking.
|
|
4377
|
+
*/
|
|
4229
4378
|
private reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId) {
|
|
4230
4379
|
this.orderSequentially(() => {
|
|
4231
4380
|
for (const message of batch) {
|