@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/api-report/container-runtime.legacy.beta.api.md +2 -0
  4. package/container-runtime.test-files.tar +0 -0
  5. package/dist/containerCompatibility.d.ts +1 -1
  6. package/dist/containerCompatibility.d.ts.map +1 -1
  7. package/dist/containerCompatibility.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +38 -11
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +118 -86
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/gc/garbageCollection.d.ts +1 -0
  13. package/dist/gc/garbageCollection.d.ts.map +1 -1
  14. package/dist/gc/garbageCollection.js +3 -8
  15. package/dist/gc/garbageCollection.js.map +1 -1
  16. package/dist/gc/gcDefinitions.d.ts +4 -0
  17. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  18. package/dist/gc/gcDefinitions.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/legacy.d.ts +1 -1
  24. package/dist/opLifecycle/batchManager.d.ts +3 -9
  25. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  26. package/dist/opLifecycle/batchManager.js +5 -3
  27. package/dist/opLifecycle/batchManager.js.map +1 -1
  28. package/dist/opLifecycle/index.d.ts +1 -1
  29. package/dist/opLifecycle/index.d.ts.map +1 -1
  30. package/dist/opLifecycle/index.js +2 -1
  31. package/dist/opLifecycle/index.js.map +1 -1
  32. package/dist/opLifecycle/opGroupingManager.d.ts +6 -0
  33. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  34. package/dist/opLifecycle/opGroupingManager.js +11 -2
  35. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  36. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  37. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  38. package/dist/opLifecycle/opSerialization.js +11 -9
  39. package/dist/opLifecycle/opSerialization.js.map +1 -1
  40. package/dist/opLifecycle/outbox.d.ts +8 -11
  41. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  42. package/dist/opLifecycle/outbox.js +42 -66
  43. package/dist/opLifecycle/outbox.js.map +1 -1
  44. package/dist/packageVersion.d.ts +1 -1
  45. package/dist/packageVersion.js +1 -1
  46. package/dist/packageVersion.js.map +1 -1
  47. package/dist/pendingStateManager.d.ts +8 -9
  48. package/dist/pendingStateManager.d.ts.map +1 -1
  49. package/dist/pendingStateManager.js +24 -22
  50. package/dist/pendingStateManager.js.map +1 -1
  51. package/dist/public.d.ts +1 -1
  52. package/dist/runtimeLayerCompatState.d.ts +2 -2
  53. package/dist/summary/documentSchema.d.ts +9 -3
  54. package/dist/summary/documentSchema.d.ts.map +1 -1
  55. package/dist/summary/documentSchema.js +19 -3
  56. package/dist/summary/documentSchema.js.map +1 -1
  57. package/dist/summary/orderedClientElection.js +2 -2
  58. package/dist/summary/orderedClientElection.js.map +1 -1
  59. package/dist/summary/summaryManager.d.ts +1 -0
  60. package/dist/summary/summaryManager.d.ts.map +1 -1
  61. package/dist/summary/summaryManager.js +9 -0
  62. package/dist/summary/summaryManager.js.map +1 -1
  63. package/eslint.config.mts +1 -1
  64. package/internal.d.ts +1 -1
  65. package/legacy.d.ts +1 -1
  66. package/lib/containerCompatibility.d.ts +1 -1
  67. package/lib/containerCompatibility.d.ts.map +1 -1
  68. package/lib/containerCompatibility.js.map +1 -1
  69. package/lib/containerRuntime.d.ts +38 -11
  70. package/lib/containerRuntime.d.ts.map +1 -1
  71. package/lib/containerRuntime.js +118 -87
  72. package/lib/containerRuntime.js.map +1 -1
  73. package/lib/gc/garbageCollection.d.ts +1 -0
  74. package/lib/gc/garbageCollection.d.ts.map +1 -1
  75. package/lib/gc/garbageCollection.js +3 -8
  76. package/lib/gc/garbageCollection.js.map +1 -1
  77. package/lib/gc/gcDefinitions.d.ts +4 -0
  78. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  79. package/lib/gc/gcDefinitions.js.map +1 -1
  80. package/lib/index.d.ts +1 -1
  81. package/lib/index.d.ts.map +1 -1
  82. package/lib/index.js +1 -1
  83. package/lib/index.js.map +1 -1
  84. package/lib/legacy.d.ts +1 -1
  85. package/lib/opLifecycle/batchManager.d.ts +3 -9
  86. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  87. package/lib/opLifecycle/batchManager.js +5 -3
  88. package/lib/opLifecycle/batchManager.js.map +1 -1
  89. package/lib/opLifecycle/index.d.ts +1 -1
  90. package/lib/opLifecycle/index.d.ts.map +1 -1
  91. package/lib/opLifecycle/index.js +1 -1
  92. package/lib/opLifecycle/index.js.map +1 -1
  93. package/lib/opLifecycle/opGroupingManager.d.ts +6 -0
  94. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  95. package/lib/opLifecycle/opGroupingManager.js +10 -1
  96. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  97. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  98. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  99. package/lib/opLifecycle/opSerialization.js +11 -9
  100. package/lib/opLifecycle/opSerialization.js.map +1 -1
  101. package/lib/opLifecycle/outbox.d.ts +8 -11
  102. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  103. package/lib/opLifecycle/outbox.js +43 -67
  104. package/lib/opLifecycle/outbox.js.map +1 -1
  105. package/lib/packageVersion.d.ts +1 -1
  106. package/lib/packageVersion.js +1 -1
  107. package/lib/packageVersion.js.map +1 -1
  108. package/lib/pendingStateManager.d.ts +8 -9
  109. package/lib/pendingStateManager.d.ts.map +1 -1
  110. package/lib/pendingStateManager.js +24 -22
  111. package/lib/pendingStateManager.js.map +1 -1
  112. package/lib/public.d.ts +1 -1
  113. package/lib/runtimeLayerCompatState.d.ts +2 -2
  114. package/lib/summary/documentSchema.d.ts +9 -3
  115. package/lib/summary/documentSchema.d.ts.map +1 -1
  116. package/lib/summary/documentSchema.js +19 -3
  117. package/lib/summary/documentSchema.js.map +1 -1
  118. package/lib/summary/orderedClientElection.js +2 -2
  119. package/lib/summary/orderedClientElection.js.map +1 -1
  120. package/lib/summary/summaryManager.d.ts +1 -0
  121. package/lib/summary/summaryManager.d.ts.map +1 -1
  122. package/lib/summary/summaryManager.js +9 -0
  123. package/lib/summary/summaryManager.js.map +1 -1
  124. package/lib/tsdoc-metadata.json +1 -1
  125. package/package.json +27 -28
  126. package/src/containerCompatibility.ts +2 -0
  127. package/src/containerRuntime.ts +163 -106
  128. package/src/gc/garbageCollection.ts +4 -9
  129. package/src/gc/gcDefinitions.ts +4 -0
  130. package/src/index.ts +1 -0
  131. package/src/opLifecycle/batchManager.ts +6 -13
  132. package/src/opLifecycle/index.ts +1 -0
  133. package/src/opLifecycle/opGroupingManager.ts +11 -1
  134. package/src/opLifecycle/opSerialization.ts +14 -12
  135. package/src/opLifecycle/outbox.ts +53 -86
  136. package/src/packageVersion.ts +1 -1
  137. package/src/pendingStateManager.ts +31 -33
  138. package/src/summary/documentSchema.ts +25 -2
  139. package/src/summary/orderedClientElection.ts +2 -2
  140. package/src/summary/summaryManager.ts +11 -0
@@ -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, gcGenerationOptionName, } from "./gc/index.js";
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, createIdCompressorFn, documentsSchemaController, featureGatesForTelemetry, provideEntryPoint, minVersionForCollab, requestHandler,
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
- // Final flush of any last staged changes
652
- // NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
653
- this.outbox.flush();
654
- this.stageControls = undefined;
655
- // During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
656
- // Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
657
- this.submitIdAllocationOpIfNeeded({ staged: false });
658
- discardOrCommit();
659
- this.channelCollection.notifyStagingMode(false);
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
- if (!this.skipSafetyFlushDuringProcessStack) {
1090
- // Reference Sequence Number may have just changed, and it must be consistent across a batch,
1091
- // so we should flush now to clear the way for the next ops.
1092
- // NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
1093
- // But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
1094
- // (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
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 after the pending state was queued need to have
1576
- // the corresponding ranges resubmitted (note this call replaces the typical resubmit flow).
1577
- // Since we don't submit ID Allocation ops when staged, any outstanding ranges would be from
1578
- // before staging mode so we can simply say staged: false.
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
- if (!this.skipSafetyFlushDuringProcessStack) {
1802
- // Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
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 and SkipFailingIncorrectSummary option is set.
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
- // If "SkipFailingIncorrectSummary" option is true, don't fail the summary in the last attempt.
2863
- // This is a fallback to make progress in documents where there are consistently pending ops in
2864
- // the summarizer.
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: "SkipFailingIncorrectSummary",
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();