@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
@@ -4,7 +4,7 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.isContainerMessageDirtyable = exports.ContainerRuntime = exports.loadContainerRuntime = exports.getSingleUseLegacyLogCallback = exports.makeLegacySendBatchFn = exports.getDeviceSpec = exports.agentSchedulerId = exports.isUnpackedRuntimeMessage = exports.defaultPendingOpsRetryDelayMs = exports.defaultPendingOpsWaitTimeoutMs = exports.defaultRuntimeHeaderData = exports.InactiveResponseHeaderKey = exports.TombstoneResponseHeaderKey = exports.DeletedResponseHeaderKey = void 0;
7
+ exports.isContainerMessageDirtyable = exports.ContainerRuntime = exports.loadContainerRuntimeAlpha = exports.loadContainerRuntime = exports.getSingleUseLegacyLogCallback = exports.makeLegacySendBatchFn = exports.getDeviceSpec = exports.agentSchedulerId = exports.isUnpackedRuntimeMessage = exports.defaultPendingOpsRetryDelayMs = exports.defaultPendingOpsWaitTimeoutMs = exports.defaultRuntimeHeaderData = exports.InactiveResponseHeaderKey = exports.TombstoneResponseHeaderKey = exports.DeletedResponseHeaderKey = void 0;
8
8
  const client_utils_1 = require("@fluid-internal/client-utils");
9
9
  const container_definitions_1 = require("@fluidframework/container-definitions");
10
10
  const internal_1 = require("@fluidframework/container-definitions/internal");
@@ -105,6 +105,15 @@ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconn
105
105
  // - we do not stringify final op, thus we do not know how much escaping will be added.
106
106
  const defaultMaxBatchSizeInBytes = 700 * 1024;
107
107
  const defaultChunkSizeInBytes = 204800;
108
+ /**
109
+ * Default maximum ops per staging-mode batch before automatic flush scheduling resumes.
110
+ *
111
+ * Chosen based on production telemetry: copy-paste operations routinely produce batches
112
+ * of 1000+ ops (435K instances over 30 days), and receivers on modern Fluid versions
113
+ * handle them without issues. Uses {@link largeBatchThreshold} to stay aligned with
114
+ * the existing "large batch" telemetry threshold ({@link OpGroupingManager}).
115
+ */
116
+ const defaultStagingModeAutoFlushThreshold = index_js_3.largeBatchThreshold;
108
117
  /**
109
118
  * The default time to wait for pending ops to be processed during summarization
110
119
  */
@@ -206,6 +215,22 @@ async function loadContainerRuntime(params) {
206
215
  return ContainerRuntime.loadRuntime(params);
207
216
  }
208
217
  exports.loadContainerRuntime = loadContainerRuntime;
218
+ /**
219
+ * Alpha variant of {@link loadContainerRuntime} that returns the runtime in an
220
+ * extendable object, allowing additional properties to be added in the future.
221
+ *
222
+ * @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
223
+ * @returns An object containing the runtime.
224
+ *
225
+ * @legacy @alpha
226
+ */
227
+ async function loadContainerRuntimeAlpha(params) {
228
+ return ContainerRuntime.loadRuntime2({
229
+ ...params,
230
+ registry: new dataStoreRegistry_js_1.FluidDataStoreRegistry(params.registryEntries),
231
+ });
232
+ }
233
+ exports.loadContainerRuntimeAlpha = loadContainerRuntimeAlpha;
209
234
  const defaultMaxConsecutiveReconnects = 7;
210
235
  /**
211
236
  * These are the ONLY message types that are allowed to be submitted while in staging mode
@@ -245,13 +270,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
245
270
  return ContainerRuntime.loadRuntime2({
246
271
  ...params,
247
272
  registry: new dataStoreRegistry_js_1.FluidDataStoreRegistry(params.registryEntries),
248
- });
273
+ }).then((r) => r.runtime);
249
274
  }
250
275
  /**
251
- * Load the stores from a snapshot and returns the runtime.
276
+ * Load the stores from a snapshot and returns an object containing the runtime.
252
277
  * @remarks
253
278
  * Same as {@link ContainerRuntime.loadRuntime},
254
279
  * but with `registry` instead of `registryEntries` and more `runtimeOptions`.
280
+ * Returns `{ runtime }` to allow future extensions (e.g. staging mode controls).
255
281
  */
256
282
  static async loadRuntime2(params) {
257
283
  const { context, registry, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab = internal_7.defaultMinVersionForCollab, } = params;
@@ -288,6 +314,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
288
314
  loadSequenceNumberVerification: "close",
289
315
  maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
290
316
  chunkSizeInBytes: defaultChunkSizeInBytes,
317
+ stagingModeAutoFlushThreshold: defaultStagingModeAutoFlushThreshold,
318
+ disableSchemaUpgrade: false,
291
319
  };
292
320
  const defaultConfigs = {
293
321
  ...defaultsAffectingDocSchema,
@@ -301,7 +329,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
301
329
  // is enabled via runtimeOptions, we will throw an error later.
302
330
  compressionOptions = enableGroupedBatching === false
303
331
  ? compressionDefinitions_js_1.disabledCompressionConfig
304
- : defaultConfigs.compressionOptions, createBlobPayloadPending = defaultConfigs.createBlobPayloadPending, } = runtimeOptions;
332
+ : defaultConfigs.compressionOptions, createBlobPayloadPending = defaultConfigs.createBlobPayloadPending, stagingModeAutoFlushThreshold = defaultConfigs.stagingModeAutoFlushThreshold, disableSchemaUpgrade = defaultConfigs.disableSchemaUpgrade, } = runtimeOptions;
305
333
  // If explicitSchemaControl is off, ensure that options which require explicitSchemaControl are not enabled.
306
334
  if (!explicitSchemaControl) {
307
335
  const disallowedKeys = Object.keys(runtimeOptions).filter((key) => containerCompatibility_js_1.runtimeOptionKeysThatRequireExplicitSchemaControl.includes(key) && runtimeOptions[key] !== undefined);
@@ -408,6 +436,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
408
436
  else {
409
437
  idCompressorMode = desiredIdCompressorMode;
410
438
  }
439
+ // eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
411
440
  const createIdCompressorFn = () => {
412
441
  /**
413
442
  * Because the IdCompressor emits so much telemetry, this function is used to sample
@@ -444,7 +473,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
444
473
  disallowedVersions: [],
445
474
  }, (schema) => {
446
475
  runtime.onSchemaChange(schema);
447
- }, { minVersionForCollab }, logger);
476
+ }, { minVersionForCollab }, logger, disableSchemaUpgrade);
448
477
  // If the minVersionForCollab for this client is greater than the existing one, we should use that one going forward.
449
478
  const existingMinVersionForCollab = documentSchemaController.sessionSchema.info.minVersionForCollab;
450
479
  const updatedMinVersionForCollab = existingMinVersionForCollab === undefined ||
@@ -468,6 +497,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
468
497
  enableGroupedBatching,
469
498
  explicitSchemaControl,
470
499
  createBlobPayloadPending,
500
+ stagingModeAutoFlushThreshold,
501
+ disableSchemaUpgrade,
471
502
  };
472
503
  (0, internal_7.validateMinimumVersionForCollab)(updatedMinVersionForCollab);
473
504
  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
@@ -478,7 +509,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
478
509
  // Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
479
510
  // or zero. This must be done before Container replays saved ops.
480
511
  await runtime.pendingStateManager.applyStashedOpsAt(runtimeSequenceNumber ?? 0);
481
- return runtime;
512
+ return { runtime };
482
513
  }
483
514
  get clientId() {
484
515
  return this._getClientId();
@@ -517,6 +548,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
517
548
  /**
518
549
  * {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.idCompressor}
519
550
  */
551
+ // eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
520
552
  get idCompressor() {
521
553
  // Expose ID Compressor only if it's On from the start.
522
554
  // If container uses delayed mode, then we can only expose generateDocumentUniqueId() and nothing else.
@@ -595,7 +627,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
595
627
  /***/
596
628
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope,
597
629
  // Create a custom ITelemetryBaseLogger to output telemetry events.
598
- baseLogger, existing, blobManagerLoadInfo, _storage, createIdCompressorFn, documentsSchemaController, featureGatesForTelemetry, provideEntryPoint, minVersionForCollab, requestHandler,
630
+ baseLogger, existing, blobManagerLoadInfo, _storage,
631
+ // eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
632
+ createIdCompressorFn, documentsSchemaController, featureGatesForTelemetry, provideEntryPoint, minVersionForCollab, requestHandler,
599
633
  // // eslint-disable-next-line unicorn/no-object-as-default-parameter
600
634
  summaryConfiguration = {
601
635
  // the defaults
@@ -652,17 +686,25 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
652
686
  // Make sure Outbox is empty before entering staging mode,
653
687
  // since we mark whole batches as "staged" or not to indicate whether to submit them.
654
688
  this.flush();
655
- const exitStagingMode = (discardOrCommit) => {
689
+ const exitStagingMode = (discardOrCommit, exitMethod) => {
656
690
  try {
657
- // Final flush of any last staged changes
658
- // NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
659
- this.outbox.flush();
660
- this.stageControls = undefined;
661
- // During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
662
- // Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
663
- this.submitIdAllocationOpIfNeeded({ staged: false });
664
- discardOrCommit();
665
- this.channelCollection.notifyStagingMode(false);
691
+ internal_8.PerformanceEvent.timedExec(this.mc.logger, {
692
+ eventName: `ExitStagingMode_${exitMethod}`,
693
+ }, (event) => {
694
+ // Final flush of any last staged changes
695
+ // NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
696
+ this.outbox.flush();
697
+ this.stageControls = undefined;
698
+ const batchInfos = discardOrCommit();
699
+ event.reportProgress({
700
+ details: {
701
+ autoFlushThreshold: this.stagingModeAutoFlushThreshold,
702
+ batches: batchInfos.length,
703
+ batchesAtOrOverThreshold: batchInfos.filter((b) => b.length >= this.stagingModeAutoFlushThreshold).length,
704
+ },
705
+ });
706
+ this.channelCollection.notifyStagingMode(false);
707
+ });
666
708
  }
667
709
  catch (error) {
668
710
  const normalizedError = (0, internal_8.normalizeError)(error);
@@ -673,21 +715,22 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
673
715
  const stageControls = {
674
716
  discardChanges: () => exitStagingMode(() => {
675
717
  // Pop all staged batches from the PSM and roll them back in LIFO order
676
- this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
718
+ const batchInfos = this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
677
719
  this.rollbackStagedChange(runtimeOp, localOpMetadata);
678
720
  });
679
721
  this.updateDocumentDirtyState();
680
- }),
722
+ return batchInfos;
723
+ }, "discard"),
681
724
  commitChanges: (options) => {
682
725
  const { squash } = { ...defaultStagingCommitOptions, ...options };
683
726
  exitStagingMode(() => {
684
727
  // Replay all staged batches in typical FIFO order.
685
728
  // We'll be out of staging mode so they'll be sent to the service finally.
686
- this.pendingStateManager.replayPendingStates({
729
+ return this.pendingStateManager.replayPendingStates({
687
730
  committingStagedBatches: true,
688
731
  squash,
689
732
  });
690
- });
733
+ }, "commit");
691
734
  },
692
735
  };
693
736
  this.stageControls = stageControls;
@@ -870,14 +913,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
870
913
  ? this.getConnectionState() === internal_1.ConnectionState.Connected ||
871
914
  this.getConnectionState() === internal_1.ConnectionState.CatchingUp
872
915
  : undefined;
873
- this.mc.logger.sendTelemetryEvent({
874
- eventName: "GCFeatureMatrix",
875
- metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
876
- inputs: JSON.stringify({
877
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
878
- gcOptions_gcGeneration: runtimeOptions.gcOptions[index_js_2.gcGenerationOptionName],
879
- }),
880
- });
881
916
  this.telemetryDocumentId = metadata?.telemetryDocumentId ?? (0, uuid_1.v4)();
882
917
  const opGroupingManager = new index_js_3.OpGroupingManager({
883
918
  groupedBatchingEnabled: this.groupedBatchingEnabled,
@@ -925,6 +960,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
925
960
  this.closeFn(error);
926
961
  throw error;
927
962
  }
963
+ this.stagingModeAutoFlushThreshold =
964
+ this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
965
+ runtimeOptions.stagingModeAutoFlushThreshold ??
966
+ defaultStagingModeAutoFlushThreshold;
928
967
  this.batchIdTrackingEnabled =
929
968
  this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
930
969
  this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
@@ -1034,9 +1073,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1034
1073
  this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
1035
1074
  this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
1036
1075
  const legacySendBatchFn = (0, exports.makeLegacySendBatchFn)(submitFn, this.innerDeltaManager);
1037
- this.skipSafetyFlushDuringProcessStack =
1038
- // Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
1039
- this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
1040
1076
  this.outbox = new index_js_3.Outbox({
1041
1077
  shouldSend: () => this.shouldSendOps(),
1042
1078
  pendingStateManager: this.pendingStateManager,
@@ -1047,8 +1083,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1047
1083
  config: {
1048
1084
  compressionOptions,
1049
1085
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1050
- // If we disable flush before process, we must be ready to flush partial batches
1051
- flushPartialBatches: this.skipSafetyFlushDuringProcessStack,
1052
1086
  },
1053
1087
  logger: this.mc.logger,
1054
1088
  groupingManager: opGroupingManager,
@@ -1059,6 +1093,24 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1059
1093
  }),
1060
1094
  reSubmit: this.reSubmit.bind(this),
1061
1095
  opReentrancy: () => this.dataModelChangeRunner.running,
1096
+ generateIdAllocationOp: () => {
1097
+ if (this._idCompressor === undefined) {
1098
+ return undefined;
1099
+ }
1100
+ const idRange = this._idCompressor.takeNextCreationRange();
1101
+ if (idRange.ids === undefined) {
1102
+ return undefined;
1103
+ }
1104
+ const idAllocationMessage = {
1105
+ type: messageTypes_js_1.ContainerMessageType.IdAllocation,
1106
+ contents: idRange,
1107
+ };
1108
+ return {
1109
+ runtimeOp: idAllocationMessage,
1110
+ referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
1111
+ staged: false,
1112
+ };
1113
+ },
1062
1114
  });
1063
1115
  this._quorum = quorum;
1064
1116
  this._quorum.on("removeMember", (clientId) => {
@@ -1092,14 +1144,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1092
1144
  // We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
1093
1145
  this.lastEmittedDirty = this.computeCurrentDirtyState();
1094
1146
  context.updateDirtyContainerState(this.lastEmittedDirty);
1095
- if (!this.skipSafetyFlushDuringProcessStack) {
1096
- // Reference Sequence Number may have just changed, and it must be consistent across a batch,
1097
- // so we should flush now to clear the way for the next ops.
1098
- // NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
1099
- // But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
1100
- // (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
1101
- this.deltaManager.on("op", () => this.flush());
1102
- }
1147
+ // Reference Sequence Number may have just changed, and it must be consistent across a batch,
1148
+ // so we should flush now to clear the way for the next ops.
1149
+ // NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
1150
+ // But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
1151
+ // (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
1152
+ this.deltaManager.on("op", () => this.flush());
1103
1153
  // logging hardware telemetry
1104
1154
  this.baseLogger.send({
1105
1155
  category: "generic",
@@ -1113,7 +1163,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1113
1163
  summaryNumber: loadSummaryNumber,
1114
1164
  summaryFormatVersion: metadata?.summaryFormatVersion,
1115
1165
  disableIsolatedChannels: metadata?.disableIsolatedChannels,
1166
+ // This is useful even for interactive clients since they track unreferenced nodes and log errors.
1116
1167
  gcVersion: metadata?.gcFeature,
1168
+ gcConfigs: this.garbageCollector.serializedConfigs,
1117
1169
  options: JSON.stringify(runtimeOptions),
1118
1170
  idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
1119
1171
  idCompressorMode: this.sessionSchema.idCompressorMode,
@@ -1121,7 +1173,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1121
1173
  featureGates: JSON.stringify({
1122
1174
  ...featureGatesForTelemetry,
1123
1175
  closeSummarizerDelayOverride,
1124
- disableFlushBeforeProcess: this.skipSafetyFlushDuringProcessStack,
1125
1176
  }),
1126
1177
  telemetryDocumentId: this.telemetryDocumentId,
1127
1178
  groupedBatchingEnabled: this.groupedBatchingEnabled,
@@ -1578,12 +1629,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1578
1629
  (0, internal_2.assert)(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
1579
1630
  this.emitDirtyDocumentEvent = false;
1580
1631
  try {
1581
- // Any ID Allocation ops that failed to submit after the pending state was queued need to have
1582
- // the corresponding ranges resubmitted (note this call replaces the typical resubmit flow).
1583
- // Since we don't submit ID Allocation ops when staged, any outstanding ranges would be from
1584
- // before staging mode so we can simply say staged: false.
1585
- this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
1586
- this.scheduleFlush();
1632
+ // Any ID Allocation ops that failed to submit need to have their ranges included
1633
+ // in the next allocation op. Reset the compressor's unfinalized range cursor so that the next
1634
+ // call to takeNextCreationRange (during replay) will include those unfinalized ranges.
1635
+ this._idCompressor?.resetUnfinalizedCreationRange();
1587
1636
  // replay the ops
1588
1637
  this.pendingStateManager.replayPendingStates();
1589
1638
  }
@@ -1804,10 +1853,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1804
1853
  // spread operator above ensure we make a shallow copy of message, as the processing flow will modify it.
1805
1854
  // There might be multiple container instances receiving the same message.
1806
1855
  this.verifyNotClosed();
1807
- if (!this.skipSafetyFlushDuringProcessStack) {
1808
- // Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
1809
- this.flush();
1810
- }
1856
+ // Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
1857
+ this.flush();
1811
1858
  this.ensureNoDataModelChanges(() => {
1812
1859
  this.processInboundMessageOrBatch(messageCopy, local);
1813
1860
  });
@@ -2583,7 +2630,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2583
2630
  outboxLength: this.outbox.messageCount,
2584
2631
  mainBatchLength: this.outbox.mainBatchMessageCount,
2585
2632
  blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
2586
- idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
2587
2633
  });
2588
2634
  }
2589
2635
  // If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
@@ -2853,7 +2899,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2853
2899
  }
2854
2900
  /**
2855
2901
  * This helper is called during summarization. If the container is dirty, it will return a failed summarize result
2856
- * (IBaseSummarizeResult) unless this is the final summarize attempt and SkipFailingIncorrectSummary option is set.
2902
+ * (IBaseSummarizeResult) unless this is the final summarize attempt, in which case the summary is allowed to
2903
+ * proceed to make progress in documents where there are consistently pending ops in the summarizer.
2857
2904
  * @param logger - The logger to be used for sending telemetry.
2858
2905
  * @param referenceSequenceNumber - The reference sequence number of the summary attempt.
2859
2906
  * @param minimumSequenceNumber - The minimum sequence number of the summary attempt.
@@ -2865,14 +2912,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2865
2912
  if (!this.isDirty) {
2866
2913
  return;
2867
2914
  }
2868
- // If "SkipFailingIncorrectSummary" option is true, don't fail the summary in the last attempt.
2869
- // This is a fallback to make progress in documents where there are consistently pending ops in
2870
- // the summarizer.
2871
- if (finalAttempt &&
2872
- this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary") === true) {
2915
+ // Don't fail the summary in the last attempt. This is a fallback to make progress in
2916
+ // documents where there are consistently pending ops in the summarizer.
2917
+ if (finalAttempt) {
2873
2918
  const error = internal_8.DataProcessingError.create("Pending ops during summarization", "submitSummary", undefined, { pendingMessages: this.pendingMessagesCount });
2874
2919
  logger.sendErrorEvent({
2875
- eventName: "SkipFailingIncorrectSummary",
2920
+ eventName: "PendingOpsDuringSummaryFinalAttempt",
2876
2921
  referenceSequenceNumber,
2877
2922
  minimumSequenceNumber,
2878
2923
  beforeGenerate: beforeSummaryGeneration,
@@ -2942,26 +2987,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2942
2987
  lookupTemporaryBlobStorageId(localId) {
2943
2988
  return this.blobManager.lookupTemporaryBlobStorageId(localId);
2944
2989
  }
2945
- submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges = false, staged, }) {
2946
- if (this._idCompressor) {
2947
- const idRange = resubmitOutstandingRanges
2948
- ? this._idCompressor.takeUnfinalizedCreationRange()
2949
- : this._idCompressor.takeNextCreationRange();
2950
- // Don't include the idRange if there weren't any Ids allocated
2951
- if (idRange.ids !== undefined) {
2952
- const idAllocationMessage = {
2953
- type: messageTypes_js_1.ContainerMessageType.IdAllocation,
2954
- contents: idRange,
2955
- };
2956
- const idAllocationBatchMessage = {
2957
- runtimeOp: idAllocationMessage,
2958
- referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
2959
- staged,
2960
- };
2961
- this.outbox.submitIdAllocation(idAllocationBatchMessage);
2962
- }
2963
- }
2964
- }
2965
2990
  submit(containerRuntimeMessage, localOpMetadata = undefined, metadata) {
2966
2991
  this.verifyNotClosed();
2967
2992
  // There should be no ops in detached container state!
@@ -2982,10 +3007,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2982
3007
  // If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
2983
3008
  const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
2984
3009
  (0, internal_2.assert)(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
2985
- // Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
2986
- if (!staged) {
2987
- this.submitIdAllocationOpIfNeeded({ staged: false });
2988
- }
2989
3010
  // Allow document schema controller to send a message if it needs to propose change in document schema.
2990
3011
  // If it needs to send a message, it will call provided callback with payload of such message and rely
2991
3012
  // on this callback to do actual sending.
@@ -3038,6 +3059,17 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3038
3059
  this.updateDocumentDirtyState();
3039
3060
  }
3040
3061
  scheduleFlush() {
3062
+ // During staging mode, suppress automatic flush scheduling until the main batch
3063
+ // reaches or exceeds the threshold.
3064
+ // Incoming ops still break the batch via direct this.flush() calls elsewhere
3065
+ // (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
3066
+ // exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
3067
+ // Additionally, outbox.outboxSequenceNumberCoherencyCheck() (called on every submit) detects
3068
+ // sequence number changes and throws if unexpected changes are detected.
3069
+ if (this.inStagingMode &&
3070
+ this.outbox.mainBatchMessageCount < this.stagingModeAutoFlushThreshold) {
3071
+ return;
3072
+ }
3041
3073
  if (this.flushScheduled) {
3042
3074
  return;
3043
3075
  }
@@ -3332,7 +3364,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3332
3364
  return internal_8.PerformanceEvent.timedExec(this.mc.logger, {
3333
3365
  eventName: "getPendingLocalState",
3334
3366
  }, (event) => {
3335
- const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3367
+ const { pending } = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3336
3368
  const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
3337
3369
  const pendingIdCompressorState = this._idCompressor?.serialize(true);
3338
3370
  const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();