@fluidframework/container-runtime 2.91.0 → 2.93.0
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 +8 -0
- package/README.md +1 -1
- package/api-report/container-runtime.legacy.beta.api.md +2 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/containerCompatibility.d.ts +1 -1
- package/dist/containerCompatibility.d.ts.map +1 -1
- package/dist/containerCompatibility.js.map +1 -1
- package/dist/containerRuntime.d.ts +38 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +118 -86
- package/dist/containerRuntime.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -0
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +3 -8
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +4 -0
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.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 +1 -1
- package/dist/opLifecycle/batchManager.d.ts +3 -9
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +5 -3
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +6 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +11 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +3 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js +11 -9
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +8 -11
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +42 -66
- package/dist/opLifecycle/outbox.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 +8 -9
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +24 -22
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/runtimeLayerCompatState.d.ts +2 -2
- package/dist/summary/documentSchema.d.ts +9 -3
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +19 -3
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/orderedClientElection.js +2 -2
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +1 -0
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +9 -0
- package/dist/summary/summaryManager.js.map +1 -1
- package/eslint.config.mts +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/containerCompatibility.d.ts +1 -1
- package/lib/containerCompatibility.d.ts.map +1 -1
- package/lib/containerCompatibility.js.map +1 -1
- package/lib/containerRuntime.d.ts +38 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +118 -87
- package/lib/containerRuntime.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -0
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +3 -8
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +4 -0
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.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 +1 -1
- package/lib/opLifecycle/batchManager.d.ts +3 -9
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +5 -3
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +6 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +10 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +3 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js +11 -9
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +8 -11
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +43 -67
- package/lib/opLifecycle/outbox.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 +8 -9
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +24 -22
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/runtimeLayerCompatState.d.ts +2 -2
- package/lib/summary/documentSchema.d.ts +9 -3
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +19 -3
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/orderedClientElection.js +2 -2
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +1 -0
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +9 -0
- package/lib/summary/summaryManager.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +27 -28
- package/src/containerCompatibility.ts +2 -0
- package/src/containerRuntime.ts +163 -106
- package/src/gc/garbageCollection.ts +4 -9
- package/src/gc/gcDefinitions.ts +4 -0
- package/src/index.ts +1 -0
- package/src/opLifecycle/batchManager.ts +6 -13
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +11 -1
- package/src/opLifecycle/opSerialization.ts +14 -12
- package/src/opLifecycle/outbox.ts +53 -86
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +31 -33
- package/src/summary/documentSchema.ts +25 -2
- package/src/summary/orderedClientElection.ts +2 -2
- package/src/summary/summaryManager.ts +11 -0
package/lib/containerRuntime.js
CHANGED
|
@@ -28,10 +28,10 @@ import { channelToDataStore } from "./dataStore.js";
|
|
|
28
28
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
|
|
29
29
|
import { BaseDeltaManagerProxy, DeltaManagerPendingOpsProxy, DeltaManagerSummarizerProxy, } from "./deltaManagerProxies.js";
|
|
30
30
|
import { DeltaScheduler } from "./deltaScheduler.js";
|
|
31
|
-
import { GCNodeType, GarbageCollector,
|
|
31
|
+
import { GCNodeType, GarbageCollector, } from "./gc/index.js";
|
|
32
32
|
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
|
|
33
33
|
import { ContainerMessageType, } from "./messageTypes.js";
|
|
34
|
-
import { DuplicateBatchDetector, ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
|
|
34
|
+
import { DuplicateBatchDetector, ensureContentsDeserialized, largeBatchThreshold, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
|
|
35
35
|
import { pkgVersion } from "./packageVersion.js";
|
|
36
36
|
import { PendingStateManager, } from "./pendingStateManager.js";
|
|
37
37
|
import { BatchRunCounter, RunCounter } from "./runCounter.js";
|
|
@@ -104,6 +104,15 @@ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconn
|
|
|
104
104
|
// - we do not stringify final op, thus we do not know how much escaping will be added.
|
|
105
105
|
const defaultMaxBatchSizeInBytes = 700 * 1024;
|
|
106
106
|
const defaultChunkSizeInBytes = 204800;
|
|
107
|
+
/**
|
|
108
|
+
* Default maximum ops per staging-mode batch before automatic flush scheduling resumes.
|
|
109
|
+
*
|
|
110
|
+
* Chosen based on production telemetry: copy-paste operations routinely produce batches
|
|
111
|
+
* of 1000+ ops (435K instances over 30 days), and receivers on modern Fluid versions
|
|
112
|
+
* handle them without issues. Uses {@link largeBatchThreshold} to stay aligned with
|
|
113
|
+
* the existing "large batch" telemetry threshold ({@link OpGroupingManager}).
|
|
114
|
+
*/
|
|
115
|
+
const defaultStagingModeAutoFlushThreshold = largeBatchThreshold;
|
|
107
116
|
/**
|
|
108
117
|
* The default time to wait for pending ops to be processed during summarization
|
|
109
118
|
*/
|
|
@@ -200,6 +209,21 @@ export let getSingleUseLegacyLogCallback = (logger, type) => {
|
|
|
200
209
|
export async function loadContainerRuntime(params) {
|
|
201
210
|
return ContainerRuntime.loadRuntime(params);
|
|
202
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Alpha variant of {@link loadContainerRuntime} that returns the runtime in an
|
|
214
|
+
* extendable object, allowing additional properties to be added in the future.
|
|
215
|
+
*
|
|
216
|
+
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
|
|
217
|
+
* @returns An object containing the runtime.
|
|
218
|
+
*
|
|
219
|
+
* @legacy @alpha
|
|
220
|
+
*/
|
|
221
|
+
export async function loadContainerRuntimeAlpha(params) {
|
|
222
|
+
return ContainerRuntime.loadRuntime2({
|
|
223
|
+
...params,
|
|
224
|
+
registry: new FluidDataStoreRegistry(params.registryEntries),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
203
227
|
const defaultMaxConsecutiveReconnects = 7;
|
|
204
228
|
/**
|
|
205
229
|
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
@@ -239,13 +263,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
239
263
|
return ContainerRuntime.loadRuntime2({
|
|
240
264
|
...params,
|
|
241
265
|
registry: new FluidDataStoreRegistry(params.registryEntries),
|
|
242
|
-
});
|
|
266
|
+
}).then((r) => r.runtime);
|
|
243
267
|
}
|
|
244
268
|
/**
|
|
245
|
-
* Load the stores from a snapshot and returns the runtime.
|
|
269
|
+
* Load the stores from a snapshot and returns an object containing the runtime.
|
|
246
270
|
* @remarks
|
|
247
271
|
* Same as {@link ContainerRuntime.loadRuntime},
|
|
248
272
|
* but with `registry` instead of `registryEntries` and more `runtimeOptions`.
|
|
273
|
+
* Returns `{ runtime }` to allow future extensions (e.g. staging mode controls).
|
|
249
274
|
*/
|
|
250
275
|
static async loadRuntime2(params) {
|
|
251
276
|
const { context, registry, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab = defaultMinVersionForCollab, } = params;
|
|
@@ -282,6 +307,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
282
307
|
loadSequenceNumberVerification: "close",
|
|
283
308
|
maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
|
|
284
309
|
chunkSizeInBytes: defaultChunkSizeInBytes,
|
|
310
|
+
stagingModeAutoFlushThreshold: defaultStagingModeAutoFlushThreshold,
|
|
311
|
+
disableSchemaUpgrade: false,
|
|
285
312
|
};
|
|
286
313
|
const defaultConfigs = {
|
|
287
314
|
...defaultsAffectingDocSchema,
|
|
@@ -295,7 +322,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
295
322
|
// is enabled via runtimeOptions, we will throw an error later.
|
|
296
323
|
compressionOptions = enableGroupedBatching === false
|
|
297
324
|
? disabledCompressionConfig
|
|
298
|
-
: defaultConfigs.compressionOptions, createBlobPayloadPending = defaultConfigs.createBlobPayloadPending, } = runtimeOptions;
|
|
325
|
+
: defaultConfigs.compressionOptions, createBlobPayloadPending = defaultConfigs.createBlobPayloadPending, stagingModeAutoFlushThreshold = defaultConfigs.stagingModeAutoFlushThreshold, disableSchemaUpgrade = defaultConfigs.disableSchemaUpgrade, } = runtimeOptions;
|
|
299
326
|
// If explicitSchemaControl is off, ensure that options which require explicitSchemaControl are not enabled.
|
|
300
327
|
if (!explicitSchemaControl) {
|
|
301
328
|
const disallowedKeys = Object.keys(runtimeOptions).filter((key) => runtimeOptionKeysThatRequireExplicitSchemaControl.includes(key) && runtimeOptions[key] !== undefined);
|
|
@@ -402,6 +429,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
402
429
|
else {
|
|
403
430
|
idCompressorMode = desiredIdCompressorMode;
|
|
404
431
|
}
|
|
432
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
405
433
|
const createIdCompressorFn = () => {
|
|
406
434
|
/**
|
|
407
435
|
* Because the IdCompressor emits so much telemetry, this function is used to sample
|
|
@@ -438,7 +466,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
438
466
|
disallowedVersions: [],
|
|
439
467
|
}, (schema) => {
|
|
440
468
|
runtime.onSchemaChange(schema);
|
|
441
|
-
}, { minVersionForCollab }, logger);
|
|
469
|
+
}, { minVersionForCollab }, logger, disableSchemaUpgrade);
|
|
442
470
|
// If the minVersionForCollab for this client is greater than the existing one, we should use that one going forward.
|
|
443
471
|
const existingMinVersionForCollab = documentSchemaController.sessionSchema.info.minVersionForCollab;
|
|
444
472
|
const updatedMinVersionForCollab = existingMinVersionForCollab === undefined ||
|
|
@@ -462,6 +490,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
462
490
|
enableGroupedBatching,
|
|
463
491
|
explicitSchemaControl,
|
|
464
492
|
createBlobPayloadPending,
|
|
493
|
+
stagingModeAutoFlushThreshold,
|
|
494
|
+
disableSchemaUpgrade,
|
|
465
495
|
};
|
|
466
496
|
validateMinimumVersionForCollab(updatedMinVersionForCollab);
|
|
467
497
|
const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, updatedMinVersionForCollab, requestHandler, undefined, // summaryConfiguration
|
|
@@ -472,7 +502,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
472
502
|
// Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
|
|
473
503
|
// or zero. This must be done before Container replays saved ops.
|
|
474
504
|
await runtime.pendingStateManager.applyStashedOpsAt(runtimeSequenceNumber ?? 0);
|
|
475
|
-
return runtime;
|
|
505
|
+
return { runtime };
|
|
476
506
|
}
|
|
477
507
|
get clientId() {
|
|
478
508
|
return this._getClientId();
|
|
@@ -511,6 +541,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
511
541
|
/**
|
|
512
542
|
* {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.idCompressor}
|
|
513
543
|
*/
|
|
544
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
514
545
|
get idCompressor() {
|
|
515
546
|
// Expose ID Compressor only if it's On from the start.
|
|
516
547
|
// If container uses delayed mode, then we can only expose generateDocumentUniqueId() and nothing else.
|
|
@@ -589,7 +620,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
589
620
|
/***/
|
|
590
621
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope,
|
|
591
622
|
// Create a custom ITelemetryBaseLogger to output telemetry events.
|
|
592
|
-
baseLogger, existing, blobManagerLoadInfo, _storage,
|
|
623
|
+
baseLogger, existing, blobManagerLoadInfo, _storage,
|
|
624
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
625
|
+
createIdCompressorFn, documentsSchemaController, featureGatesForTelemetry, provideEntryPoint, minVersionForCollab, requestHandler,
|
|
593
626
|
// // eslint-disable-next-line unicorn/no-object-as-default-parameter
|
|
594
627
|
summaryConfiguration = {
|
|
595
628
|
// the defaults
|
|
@@ -646,17 +679,25 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
646
679
|
// Make sure Outbox is empty before entering staging mode,
|
|
647
680
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
648
681
|
this.flush();
|
|
649
|
-
const exitStagingMode = (discardOrCommit) => {
|
|
682
|
+
const exitStagingMode = (discardOrCommit, exitMethod) => {
|
|
650
683
|
try {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
684
|
+
PerformanceEvent.timedExec(this.mc.logger, {
|
|
685
|
+
eventName: `ExitStagingMode_${exitMethod}`,
|
|
686
|
+
}, (event) => {
|
|
687
|
+
// Final flush of any last staged changes
|
|
688
|
+
// NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
|
|
689
|
+
this.outbox.flush();
|
|
690
|
+
this.stageControls = undefined;
|
|
691
|
+
const batchInfos = discardOrCommit();
|
|
692
|
+
event.reportProgress({
|
|
693
|
+
details: {
|
|
694
|
+
autoFlushThreshold: this.stagingModeAutoFlushThreshold,
|
|
695
|
+
batches: batchInfos.length,
|
|
696
|
+
batchesAtOrOverThreshold: batchInfos.filter((b) => b.length >= this.stagingModeAutoFlushThreshold).length,
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
this.channelCollection.notifyStagingMode(false);
|
|
700
|
+
});
|
|
660
701
|
}
|
|
661
702
|
catch (error) {
|
|
662
703
|
const normalizedError = normalizeError(error);
|
|
@@ -667,21 +708,22 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
667
708
|
const stageControls = {
|
|
668
709
|
discardChanges: () => exitStagingMode(() => {
|
|
669
710
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
670
|
-
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
711
|
+
const batchInfos = this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
671
712
|
this.rollbackStagedChange(runtimeOp, localOpMetadata);
|
|
672
713
|
});
|
|
673
714
|
this.updateDocumentDirtyState();
|
|
674
|
-
|
|
715
|
+
return batchInfos;
|
|
716
|
+
}, "discard"),
|
|
675
717
|
commitChanges: (options) => {
|
|
676
718
|
const { squash } = { ...defaultStagingCommitOptions, ...options };
|
|
677
719
|
exitStagingMode(() => {
|
|
678
720
|
// Replay all staged batches in typical FIFO order.
|
|
679
721
|
// We'll be out of staging mode so they'll be sent to the service finally.
|
|
680
|
-
this.pendingStateManager.replayPendingStates({
|
|
722
|
+
return this.pendingStateManager.replayPendingStates({
|
|
681
723
|
committingStagedBatches: true,
|
|
682
724
|
squash,
|
|
683
725
|
});
|
|
684
|
-
});
|
|
726
|
+
}, "commit");
|
|
685
727
|
},
|
|
686
728
|
};
|
|
687
729
|
this.stageControls = stageControls;
|
|
@@ -864,14 +906,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
864
906
|
? this.getConnectionState() === ConnectionState.Connected ||
|
|
865
907
|
this.getConnectionState() === ConnectionState.CatchingUp
|
|
866
908
|
: undefined;
|
|
867
|
-
this.mc.logger.sendTelemetryEvent({
|
|
868
|
-
eventName: "GCFeatureMatrix",
|
|
869
|
-
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
870
|
-
inputs: JSON.stringify({
|
|
871
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
872
|
-
gcOptions_gcGeneration: runtimeOptions.gcOptions[gcGenerationOptionName],
|
|
873
|
-
}),
|
|
874
|
-
});
|
|
875
909
|
this.telemetryDocumentId = metadata?.telemetryDocumentId ?? uuid();
|
|
876
910
|
const opGroupingManager = new OpGroupingManager({
|
|
877
911
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -919,6 +953,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
919
953
|
this.closeFn(error);
|
|
920
954
|
throw error;
|
|
921
955
|
}
|
|
956
|
+
this.stagingModeAutoFlushThreshold =
|
|
957
|
+
this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
|
|
958
|
+
runtimeOptions.stagingModeAutoFlushThreshold ??
|
|
959
|
+
defaultStagingModeAutoFlushThreshold;
|
|
922
960
|
this.batchIdTrackingEnabled =
|
|
923
961
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
|
|
924
962
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
|
|
@@ -1028,9 +1066,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1028
1066
|
this.deltaScheduler = new DeltaScheduler(this.innerDeltaManager, this, createChildLogger({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
|
|
1029
1067
|
this.inboundBatchAggregator = new InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, createChildLogger({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
|
|
1030
1068
|
const legacySendBatchFn = makeLegacySendBatchFn(submitFn, this.innerDeltaManager);
|
|
1031
|
-
this.skipSafetyFlushDuringProcessStack =
|
|
1032
|
-
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
1033
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
1034
1069
|
this.outbox = new Outbox({
|
|
1035
1070
|
shouldSend: () => this.shouldSendOps(),
|
|
1036
1071
|
pendingStateManager: this.pendingStateManager,
|
|
@@ -1041,8 +1076,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1041
1076
|
config: {
|
|
1042
1077
|
compressionOptions,
|
|
1043
1078
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1044
|
-
// If we disable flush before process, we must be ready to flush partial batches
|
|
1045
|
-
flushPartialBatches: this.skipSafetyFlushDuringProcessStack,
|
|
1046
1079
|
},
|
|
1047
1080
|
logger: this.mc.logger,
|
|
1048
1081
|
groupingManager: opGroupingManager,
|
|
@@ -1053,6 +1086,24 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1053
1086
|
}),
|
|
1054
1087
|
reSubmit: this.reSubmit.bind(this),
|
|
1055
1088
|
opReentrancy: () => this.dataModelChangeRunner.running,
|
|
1089
|
+
generateIdAllocationOp: () => {
|
|
1090
|
+
if (this._idCompressor === undefined) {
|
|
1091
|
+
return undefined;
|
|
1092
|
+
}
|
|
1093
|
+
const idRange = this._idCompressor.takeNextCreationRange();
|
|
1094
|
+
if (idRange.ids === undefined) {
|
|
1095
|
+
return undefined;
|
|
1096
|
+
}
|
|
1097
|
+
const idAllocationMessage = {
|
|
1098
|
+
type: ContainerMessageType.IdAllocation,
|
|
1099
|
+
contents: idRange,
|
|
1100
|
+
};
|
|
1101
|
+
return {
|
|
1102
|
+
runtimeOp: idAllocationMessage,
|
|
1103
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1104
|
+
staged: false,
|
|
1105
|
+
};
|
|
1106
|
+
},
|
|
1056
1107
|
});
|
|
1057
1108
|
this._quorum = quorum;
|
|
1058
1109
|
this._quorum.on("removeMember", (clientId) => {
|
|
@@ -1086,14 +1137,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1086
1137
|
// We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
|
|
1087
1138
|
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
1088
1139
|
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
this.deltaManager.on("op", () => this.flush());
|
|
1096
|
-
}
|
|
1140
|
+
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
1141
|
+
// so we should flush now to clear the way for the next ops.
|
|
1142
|
+
// NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
|
|
1143
|
+
// But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
|
|
1144
|
+
// (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
|
|
1145
|
+
this.deltaManager.on("op", () => this.flush());
|
|
1097
1146
|
// logging hardware telemetry
|
|
1098
1147
|
this.baseLogger.send({
|
|
1099
1148
|
category: "generic",
|
|
@@ -1107,7 +1156,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1107
1156
|
summaryNumber: loadSummaryNumber,
|
|
1108
1157
|
summaryFormatVersion: metadata?.summaryFormatVersion,
|
|
1109
1158
|
disableIsolatedChannels: metadata?.disableIsolatedChannels,
|
|
1159
|
+
// This is useful even for interactive clients since they track unreferenced nodes and log errors.
|
|
1110
1160
|
gcVersion: metadata?.gcFeature,
|
|
1161
|
+
gcConfigs: this.garbageCollector.serializedConfigs,
|
|
1111
1162
|
options: JSON.stringify(runtimeOptions),
|
|
1112
1163
|
idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
|
|
1113
1164
|
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
@@ -1115,7 +1166,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1115
1166
|
featureGates: JSON.stringify({
|
|
1116
1167
|
...featureGatesForTelemetry,
|
|
1117
1168
|
closeSummarizerDelayOverride,
|
|
1118
|
-
disableFlushBeforeProcess: this.skipSafetyFlushDuringProcessStack,
|
|
1119
1169
|
}),
|
|
1120
1170
|
telemetryDocumentId: this.telemetryDocumentId,
|
|
1121
1171
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -1572,12 +1622,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1572
1622
|
assert(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
|
|
1573
1623
|
this.emitDirtyDocumentEvent = false;
|
|
1574
1624
|
try {
|
|
1575
|
-
// Any ID Allocation ops that failed to submit
|
|
1576
|
-
// the
|
|
1577
|
-
//
|
|
1578
|
-
|
|
1579
|
-
this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
|
|
1580
|
-
this.scheduleFlush();
|
|
1625
|
+
// Any ID Allocation ops that failed to submit need to have their ranges included
|
|
1626
|
+
// in the next allocation op. Reset the compressor's unfinalized range cursor so that the next
|
|
1627
|
+
// call to takeNextCreationRange (during replay) will include those unfinalized ranges.
|
|
1628
|
+
this._idCompressor?.resetUnfinalizedCreationRange();
|
|
1581
1629
|
// replay the ops
|
|
1582
1630
|
this.pendingStateManager.replayPendingStates();
|
|
1583
1631
|
}
|
|
@@ -1798,10 +1846,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1798
1846
|
// spread operator above ensure we make a shallow copy of message, as the processing flow will modify it.
|
|
1799
1847
|
// There might be multiple container instances receiving the same message.
|
|
1800
1848
|
this.verifyNotClosed();
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
this.flush();
|
|
1804
|
-
}
|
|
1849
|
+
// Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
|
|
1850
|
+
this.flush();
|
|
1805
1851
|
this.ensureNoDataModelChanges(() => {
|
|
1806
1852
|
this.processInboundMessageOrBatch(messageCopy, local);
|
|
1807
1853
|
});
|
|
@@ -2577,7 +2623,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2577
2623
|
outboxLength: this.outbox.messageCount,
|
|
2578
2624
|
mainBatchLength: this.outbox.mainBatchMessageCount,
|
|
2579
2625
|
blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
|
|
2580
|
-
idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
|
|
2581
2626
|
});
|
|
2582
2627
|
}
|
|
2583
2628
|
// If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
|
|
@@ -2847,7 +2892,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2847
2892
|
}
|
|
2848
2893
|
/**
|
|
2849
2894
|
* This helper is called during summarization. If the container is dirty, it will return a failed summarize result
|
|
2850
|
-
* (IBaseSummarizeResult) unless this is the final summarize attempt
|
|
2895
|
+
* (IBaseSummarizeResult) unless this is the final summarize attempt, in which case the summary is allowed to
|
|
2896
|
+
* proceed to make progress in documents where there are consistently pending ops in the summarizer.
|
|
2851
2897
|
* @param logger - The logger to be used for sending telemetry.
|
|
2852
2898
|
* @param referenceSequenceNumber - The reference sequence number of the summary attempt.
|
|
2853
2899
|
* @param minimumSequenceNumber - The minimum sequence number of the summary attempt.
|
|
@@ -2859,14 +2905,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2859
2905
|
if (!this.isDirty) {
|
|
2860
2906
|
return;
|
|
2861
2907
|
}
|
|
2862
|
-
//
|
|
2863
|
-
//
|
|
2864
|
-
|
|
2865
|
-
if (finalAttempt &&
|
|
2866
|
-
this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary") === true) {
|
|
2908
|
+
// Don't fail the summary in the last attempt. This is a fallback to make progress in
|
|
2909
|
+
// documents where there are consistently pending ops in the summarizer.
|
|
2910
|
+
if (finalAttempt) {
|
|
2867
2911
|
const error = DataProcessingError.create("Pending ops during summarization", "submitSummary", undefined, { pendingMessages: this.pendingMessagesCount });
|
|
2868
2912
|
logger.sendErrorEvent({
|
|
2869
|
-
eventName: "
|
|
2913
|
+
eventName: "PendingOpsDuringSummaryFinalAttempt",
|
|
2870
2914
|
referenceSequenceNumber,
|
|
2871
2915
|
minimumSequenceNumber,
|
|
2872
2916
|
beforeGenerate: beforeSummaryGeneration,
|
|
@@ -2936,26 +2980,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2936
2980
|
lookupTemporaryBlobStorageId(localId) {
|
|
2937
2981
|
return this.blobManager.lookupTemporaryBlobStorageId(localId);
|
|
2938
2982
|
}
|
|
2939
|
-
submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges = false, staged, }) {
|
|
2940
|
-
if (this._idCompressor) {
|
|
2941
|
-
const idRange = resubmitOutstandingRanges
|
|
2942
|
-
? this._idCompressor.takeUnfinalizedCreationRange()
|
|
2943
|
-
: this._idCompressor.takeNextCreationRange();
|
|
2944
|
-
// Don't include the idRange if there weren't any Ids allocated
|
|
2945
|
-
if (idRange.ids !== undefined) {
|
|
2946
|
-
const idAllocationMessage = {
|
|
2947
|
-
type: ContainerMessageType.IdAllocation,
|
|
2948
|
-
contents: idRange,
|
|
2949
|
-
};
|
|
2950
|
-
const idAllocationBatchMessage = {
|
|
2951
|
-
runtimeOp: idAllocationMessage,
|
|
2952
|
-
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
2953
|
-
staged,
|
|
2954
|
-
};
|
|
2955
|
-
this.outbox.submitIdAllocation(idAllocationBatchMessage);
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
2983
|
submit(containerRuntimeMessage, localOpMetadata = undefined, metadata) {
|
|
2960
2984
|
this.verifyNotClosed();
|
|
2961
2985
|
// There should be no ops in detached container state!
|
|
@@ -2976,10 +3000,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2976
3000
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
2977
3001
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
2978
3002
|
assert(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
|
|
2979
|
-
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
2980
|
-
if (!staged) {
|
|
2981
|
-
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
2982
|
-
}
|
|
2983
3003
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
2984
3004
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
2985
3005
|
// on this callback to do actual sending.
|
|
@@ -3032,6 +3052,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3032
3052
|
this.updateDocumentDirtyState();
|
|
3033
3053
|
}
|
|
3034
3054
|
scheduleFlush() {
|
|
3055
|
+
// During staging mode, suppress automatic flush scheduling until the main batch
|
|
3056
|
+
// reaches or exceeds the threshold.
|
|
3057
|
+
// Incoming ops still break the batch via direct this.flush() calls elsewhere
|
|
3058
|
+
// (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
|
|
3059
|
+
// exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
|
|
3060
|
+
// Additionally, outbox.outboxSequenceNumberCoherencyCheck() (called on every submit) detects
|
|
3061
|
+
// sequence number changes and throws if unexpected changes are detected.
|
|
3062
|
+
if (this.inStagingMode &&
|
|
3063
|
+
this.outbox.mainBatchMessageCount < this.stagingModeAutoFlushThreshold) {
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3035
3066
|
if (this.flushScheduled) {
|
|
3036
3067
|
return;
|
|
3037
3068
|
}
|
|
@@ -3326,7 +3357,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3326
3357
|
return PerformanceEvent.timedExec(this.mc.logger, {
|
|
3327
3358
|
eventName: "getPendingLocalState",
|
|
3328
3359
|
}, (event) => {
|
|
3329
|
-
const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
3360
|
+
const { pending } = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
3330
3361
|
const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
3331
3362
|
const pendingIdCompressorState = this._idCompressor?.serialize(true);
|
|
3332
3363
|
const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
|