@fluidframework/container-runtime 2.90.0 → 2.92.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/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 +37 -10
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +105 -77
- 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.map +1 -1
- package/dist/opLifecycle/batchManager.js +2 -1
- 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 +0 -6
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +2 -9
- 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 +7 -3
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +19 -7
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/runtimeLayerCompatState.d.ts +1 -1
- 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 +9 -0
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +29 -0
- package/dist/summary/summaryManager.js.map +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 +37 -10
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +106 -79
- 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.map +1 -1
- package/lib/opLifecycle/batchManager.js +2 -1
- 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 +0 -6
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +2 -9
- 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 +7 -3
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +19 -7
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/runtimeLayerCompatState.d.ts +1 -1
- 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 +9 -0
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +29 -0
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +28 -24
- package/src/containerCompatibility.ts +2 -0
- package/src/containerRuntime.ts +153 -93
- 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 +2 -1
- 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 +2 -17
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +27 -11
- package/src/summary/documentSchema.ts +25 -2
- package/src/summary/orderedClientElection.ts +2 -2
- package/src/summary/summaryManager.ts +32 -0
package/lib/containerRuntime.js
CHANGED
|
@@ -10,7 +10,7 @@ import { SummaryType } from "@fluidframework/driver-definitions";
|
|
|
10
10
|
import { FetchSource, MessageType } from "@fluidframework/driver-definitions/internal";
|
|
11
11
|
import { readAndParse } from "@fluidframework/driver-utils/internal";
|
|
12
12
|
import { createIdCompressor, createSessionId, deserializeIdCompressor, } from "@fluidframework/id-compressor/internal";
|
|
13
|
-
import { FlushMode,
|
|
13
|
+
import { FlushMode, channelsTreeName, gcTreeKey, } from "@fluidframework/runtime-definitions/internal";
|
|
14
14
|
import { addBlobToSummary, addSummarizeResultToSummary, calculateStats, create404Response, defaultMinVersionForCollab, exceptionToResponse, GCDataBuilder, isValidMinVersionForCollab, RequestParser, RuntimeHeaders, validateMinimumVersionForCollab, seqFromTree, TelemetryContext, } from "@fluidframework/runtime-utils/internal";
|
|
15
15
|
import { DataCorruptionError, DataProcessingError, extractSafePropertiesFromMessage, GenericError, LoggingError, PerformanceEvent,
|
|
16
16
|
// eslint-disable-next-line import-x/no-deprecated
|
|
@@ -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,28 @@ 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
|
+
// During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
|
|
692
|
+
// Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
|
|
693
|
+
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
694
|
+
const batchInfos = discardOrCommit();
|
|
695
|
+
event.reportProgress({
|
|
696
|
+
details: {
|
|
697
|
+
autoFlushThreshold: this.stagingModeAutoFlushThreshold,
|
|
698
|
+
batches: batchInfos.length,
|
|
699
|
+
batchesAtOrOverThreshold: batchInfos.filter((b) => b.length >= this.stagingModeAutoFlushThreshold).length,
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
this.channelCollection.notifyStagingMode(false);
|
|
703
|
+
});
|
|
660
704
|
}
|
|
661
705
|
catch (error) {
|
|
662
706
|
const normalizedError = normalizeError(error);
|
|
@@ -667,21 +711,22 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
667
711
|
const stageControls = {
|
|
668
712
|
discardChanges: () => exitStagingMode(() => {
|
|
669
713
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
670
|
-
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
714
|
+
const batchInfos = this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
671
715
|
this.rollbackStagedChange(runtimeOp, localOpMetadata);
|
|
672
716
|
});
|
|
673
717
|
this.updateDocumentDirtyState();
|
|
674
|
-
|
|
718
|
+
return batchInfos;
|
|
719
|
+
}, "discard"),
|
|
675
720
|
commitChanges: (options) => {
|
|
676
721
|
const { squash } = { ...defaultStagingCommitOptions, ...options };
|
|
677
722
|
exitStagingMode(() => {
|
|
678
723
|
// Replay all staged batches in typical FIFO order.
|
|
679
724
|
// We'll be out of staging mode so they'll be sent to the service finally.
|
|
680
|
-
this.pendingStateManager.replayPendingStates({
|
|
725
|
+
return this.pendingStateManager.replayPendingStates({
|
|
681
726
|
committingStagedBatches: true,
|
|
682
727
|
squash,
|
|
683
728
|
});
|
|
684
|
-
});
|
|
729
|
+
}, "commit");
|
|
685
730
|
},
|
|
686
731
|
};
|
|
687
732
|
this.stageControls = stageControls;
|
|
@@ -742,7 +787,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
742
787
|
}
|
|
743
788
|
return eventEmitter;
|
|
744
789
|
});
|
|
745
|
-
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, signalAudience, pendingLocalState,
|
|
790
|
+
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, signalAudience, pendingLocalState, snapshotWithContents, getConnectionState, } = context;
|
|
746
791
|
this.getConnectionState = getConnectionState;
|
|
747
792
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
748
793
|
this.disposeFn = disposeFn ?? closeFn;
|
|
@@ -864,14 +909,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
864
909
|
? this.getConnectionState() === ConnectionState.Connected ||
|
|
865
910
|
this.getConnectionState() === ConnectionState.CatchingUp
|
|
866
911
|
: 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
912
|
this.telemetryDocumentId = metadata?.telemetryDocumentId ?? uuid();
|
|
876
913
|
const opGroupingManager = new OpGroupingManager({
|
|
877
914
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -912,20 +949,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
912
949
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.Test.DisableSummaries") === true;
|
|
913
950
|
this.maxConsecutiveReconnects =
|
|
914
951
|
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
|
|
915
|
-
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
!referenceSequenceNumbersSupported) {
|
|
922
|
-
// The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
|
|
923
|
-
this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
|
|
924
|
-
this._flushMode = FlushMode.TurnBased;
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
this._flushMode = runtimeOptions.flushMode;
|
|
952
|
+
this._flushMode = runtimeOptions.flushMode;
|
|
953
|
+
// TODO: Added in 2.90.0 - Remove this validation once we've released and confirmed no consumer passes an invalid flushMode value.
|
|
954
|
+
if (this._flushMode !== FlushMode.Immediate && this._flushMode !== FlushMode.TurnBased) {
|
|
955
|
+
const error = new UsageError("Invalid flushMode runtime option. Expected FlushMode.Immediate or FlushMode.TurnBased.");
|
|
956
|
+
this.closeFn(error);
|
|
957
|
+
throw error;
|
|
928
958
|
}
|
|
959
|
+
this.stagingModeAutoFlushThreshold =
|
|
960
|
+
this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
|
|
961
|
+
runtimeOptions.stagingModeAutoFlushThreshold ??
|
|
962
|
+
defaultStagingModeAutoFlushThreshold;
|
|
929
963
|
this.batchIdTrackingEnabled =
|
|
930
964
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
|
|
931
965
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
|
|
@@ -1035,9 +1069,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1035
1069
|
this.deltaScheduler = new DeltaScheduler(this.innerDeltaManager, this, createChildLogger({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
|
|
1036
1070
|
this.inboundBatchAggregator = new InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, createChildLogger({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
|
|
1037
1071
|
const legacySendBatchFn = makeLegacySendBatchFn(submitFn, this.innerDeltaManager);
|
|
1038
|
-
this.skipSafetyFlushDuringProcessStack =
|
|
1039
|
-
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
1040
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
1041
1072
|
this.outbox = new Outbox({
|
|
1042
1073
|
shouldSend: () => this.shouldSendOps(),
|
|
1043
1074
|
pendingStateManager: this.pendingStateManager,
|
|
@@ -1048,8 +1079,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1048
1079
|
config: {
|
|
1049
1080
|
compressionOptions,
|
|
1050
1081
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1051
|
-
// If we disable flush before process, we must be ready to flush partial batches
|
|
1052
|
-
flushPartialBatches: this.skipSafetyFlushDuringProcessStack,
|
|
1053
1082
|
},
|
|
1054
1083
|
logger: this.mc.logger,
|
|
1055
1084
|
groupingManager: opGroupingManager,
|
|
@@ -1093,14 +1122,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1093
1122
|
// We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
|
|
1094
1123
|
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
1095
1124
|
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
this.deltaManager.on("op", () => this.flush());
|
|
1103
|
-
}
|
|
1125
|
+
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
1126
|
+
// so we should flush now to clear the way for the next ops.
|
|
1127
|
+
// NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
|
|
1128
|
+
// But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
|
|
1129
|
+
// (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
|
|
1130
|
+
this.deltaManager.on("op", () => this.flush());
|
|
1104
1131
|
// logging hardware telemetry
|
|
1105
1132
|
this.baseLogger.send({
|
|
1106
1133
|
category: "generic",
|
|
@@ -1114,7 +1141,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1114
1141
|
summaryNumber: loadSummaryNumber,
|
|
1115
1142
|
summaryFormatVersion: metadata?.summaryFormatVersion,
|
|
1116
1143
|
disableIsolatedChannels: metadata?.disableIsolatedChannels,
|
|
1144
|
+
// This is useful even for interactive clients since they track unreferenced nodes and log errors.
|
|
1117
1145
|
gcVersion: metadata?.gcFeature,
|
|
1146
|
+
gcConfigs: this.garbageCollector.serializedConfigs,
|
|
1118
1147
|
options: JSON.stringify(runtimeOptions),
|
|
1119
1148
|
idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
|
|
1120
1149
|
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
@@ -1122,7 +1151,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1122
1151
|
featureGates: JSON.stringify({
|
|
1123
1152
|
...featureGatesForTelemetry,
|
|
1124
1153
|
closeSummarizerDelayOverride,
|
|
1125
|
-
disableFlushBeforeProcess: this.skipSafetyFlushDuringProcessStack,
|
|
1126
1154
|
}),
|
|
1127
1155
|
telemetryDocumentId: this.telemetryDocumentId,
|
|
1128
1156
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -1579,12 +1607,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1579
1607
|
assert(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
|
|
1580
1608
|
this.emitDirtyDocumentEvent = false;
|
|
1581
1609
|
try {
|
|
1582
|
-
// Any ID Allocation ops that failed to submit
|
|
1583
|
-
// the
|
|
1584
|
-
//
|
|
1585
|
-
|
|
1586
|
-
this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
|
|
1587
|
-
this.scheduleFlush();
|
|
1610
|
+
// Any ID Allocation ops that failed to submit need to have their ranges included
|
|
1611
|
+
// in the next allocation op. Reset the compressor's unfinalized range cursor so that the next
|
|
1612
|
+
// call to takeNextCreationRange (during replay) will include those unfinalized ranges.
|
|
1613
|
+
this._idCompressor?.resetUnfinalizedCreationRange();
|
|
1588
1614
|
// replay the ops
|
|
1589
1615
|
this.pendingStateManager.replayPendingStates();
|
|
1590
1616
|
}
|
|
@@ -1805,10 +1831,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1805
1831
|
// spread operator above ensure we make a shallow copy of message, as the processing flow will modify it.
|
|
1806
1832
|
// There might be multiple container instances receiving the same message.
|
|
1807
1833
|
this.verifyNotClosed();
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
this.flush();
|
|
1811
|
-
}
|
|
1834
|
+
// Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
|
|
1835
|
+
this.flush();
|
|
1812
1836
|
this.ensureNoDataModelChanges(() => {
|
|
1813
1837
|
this.processInboundMessageOrBatch(messageCopy, local);
|
|
1814
1838
|
});
|
|
@@ -2299,7 +2323,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2299
2323
|
return this.lastEmittedDirty;
|
|
2300
2324
|
}
|
|
2301
2325
|
/**
|
|
2302
|
-
* Returns true if the container is dirty: not attached, or
|
|
2326
|
+
* Returns true if the container is dirty: not attached, or has pending user messages (ignores "non-dirtyable" ones though)
|
|
2303
2327
|
*/
|
|
2304
2328
|
computeCurrentDirtyState() {
|
|
2305
2329
|
return (this.attachState !== AttachState.Attached ||
|
|
@@ -3039,6 +3063,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3039
3063
|
this.updateDocumentDirtyState();
|
|
3040
3064
|
}
|
|
3041
3065
|
scheduleFlush() {
|
|
3066
|
+
// During staging mode, suppress automatic flush scheduling until the main batch
|
|
3067
|
+
// reaches or exceeds the threshold.
|
|
3068
|
+
// Incoming ops still break the batch via direct this.flush() calls elsewhere
|
|
3069
|
+
// (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
|
|
3070
|
+
// exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
|
|
3071
|
+
// Additionally, outbox.maybeFlushPartialBatch() (called on every submit) detects
|
|
3072
|
+
// sequence number changes and throws if unexpected changes are detected.
|
|
3073
|
+
if (this.inStagingMode &&
|
|
3074
|
+
this.outbox.mainBatchMessageCount < this.stagingModeAutoFlushThreshold) {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3042
3077
|
if (this.flushScheduled) {
|
|
3043
3078
|
return;
|
|
3044
3079
|
}
|
|
@@ -3058,14 +3093,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3058
3093
|
Promise.resolve().then(() => this.flush());
|
|
3059
3094
|
break;
|
|
3060
3095
|
}
|
|
3061
|
-
// FlushModeExperimental is experimental and not exposed directly in the runtime APIs
|
|
3062
|
-
case FlushModeExperimental.Async: {
|
|
3063
|
-
// When in Async flush mode, the runtime will accumulate all operations across JS turns and send them as a single
|
|
3064
|
-
// batch when all micro-tasks are complete.
|
|
3065
|
-
// Compared to TurnBased, this flush mode will capture more ops into the same batch.
|
|
3066
|
-
setTimeout(() => this.flush(), 0);
|
|
3067
|
-
break;
|
|
3068
|
-
}
|
|
3069
3096
|
default: {
|
|
3070
3097
|
fail(0x587 /* Unreachable unless manually accumulating a batch */);
|
|
3071
3098
|
}
|
|
@@ -3341,7 +3368,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3341
3368
|
return PerformanceEvent.timedExec(this.mc.logger, {
|
|
3342
3369
|
eventName: "getPendingLocalState",
|
|
3343
3370
|
}, (event) => {
|
|
3344
|
-
const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
3371
|
+
const { pending } = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
3345
3372
|
const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
3346
3373
|
const pendingIdCompressorState = this._idCompressor?.serialize(true);
|
|
3347
3374
|
const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
|