@fluidframework/container-runtime 2.91.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.beta.api.md +2 -0
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/containerCompatibility.d.ts +1 -1
  5. package/dist/containerCompatibility.d.ts.map +1 -1
  6. package/dist/containerCompatibility.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +36 -9
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +97 -54
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/gc/garbageCollection.d.ts +1 -0
  12. package/dist/gc/garbageCollection.d.ts.map +1 -1
  13. package/dist/gc/garbageCollection.js +3 -8
  14. package/dist/gc/garbageCollection.js.map +1 -1
  15. package/dist/gc/gcDefinitions.d.ts +4 -0
  16. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  17. package/dist/gc/gcDefinitions.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/legacy.d.ts +1 -1
  23. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  24. package/dist/opLifecycle/batchManager.js +2 -1
  25. package/dist/opLifecycle/batchManager.js.map +1 -1
  26. package/dist/opLifecycle/index.d.ts +1 -1
  27. package/dist/opLifecycle/index.d.ts.map +1 -1
  28. package/dist/opLifecycle/index.js +2 -1
  29. package/dist/opLifecycle/index.js.map +1 -1
  30. package/dist/opLifecycle/opGroupingManager.d.ts +6 -0
  31. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  32. package/dist/opLifecycle/opGroupingManager.js +11 -2
  33. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  34. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  35. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  36. package/dist/opLifecycle/opSerialization.js +11 -9
  37. package/dist/opLifecycle/opSerialization.js.map +1 -1
  38. package/dist/opLifecycle/outbox.d.ts +0 -6
  39. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  40. package/dist/opLifecycle/outbox.js +2 -9
  41. package/dist/opLifecycle/outbox.js.map +1 -1
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/pendingStateManager.d.ts +7 -3
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +19 -7
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/public.d.ts +1 -1
  50. package/dist/runtimeLayerCompatState.d.ts +1 -1
  51. package/dist/summary/documentSchema.d.ts +9 -3
  52. package/dist/summary/documentSchema.d.ts.map +1 -1
  53. package/dist/summary/documentSchema.js +19 -3
  54. package/dist/summary/documentSchema.js.map +1 -1
  55. package/dist/summary/orderedClientElection.js +2 -2
  56. package/dist/summary/orderedClientElection.js.map +1 -1
  57. package/dist/summary/summaryManager.d.ts +1 -0
  58. package/dist/summary/summaryManager.d.ts.map +1 -1
  59. package/dist/summary/summaryManager.js +9 -0
  60. package/dist/summary/summaryManager.js.map +1 -1
  61. package/internal.d.ts +1 -1
  62. package/legacy.d.ts +1 -1
  63. package/lib/containerCompatibility.d.ts +1 -1
  64. package/lib/containerCompatibility.d.ts.map +1 -1
  65. package/lib/containerCompatibility.js.map +1 -1
  66. package/lib/containerRuntime.d.ts +36 -9
  67. package/lib/containerRuntime.d.ts.map +1 -1
  68. package/lib/containerRuntime.js +97 -55
  69. package/lib/containerRuntime.js.map +1 -1
  70. package/lib/gc/garbageCollection.d.ts +1 -0
  71. package/lib/gc/garbageCollection.d.ts.map +1 -1
  72. package/lib/gc/garbageCollection.js +3 -8
  73. package/lib/gc/garbageCollection.js.map +1 -1
  74. package/lib/gc/gcDefinitions.d.ts +4 -0
  75. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  76. package/lib/gc/gcDefinitions.js.map +1 -1
  77. package/lib/index.d.ts +1 -1
  78. package/lib/index.d.ts.map +1 -1
  79. package/lib/index.js +1 -1
  80. package/lib/index.js.map +1 -1
  81. package/lib/legacy.d.ts +1 -1
  82. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  83. package/lib/opLifecycle/batchManager.js +2 -1
  84. package/lib/opLifecycle/batchManager.js.map +1 -1
  85. package/lib/opLifecycle/index.d.ts +1 -1
  86. package/lib/opLifecycle/index.d.ts.map +1 -1
  87. package/lib/opLifecycle/index.js +1 -1
  88. package/lib/opLifecycle/index.js.map +1 -1
  89. package/lib/opLifecycle/opGroupingManager.d.ts +6 -0
  90. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  91. package/lib/opLifecycle/opGroupingManager.js +10 -1
  92. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  93. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  94. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  95. package/lib/opLifecycle/opSerialization.js +11 -9
  96. package/lib/opLifecycle/opSerialization.js.map +1 -1
  97. package/lib/opLifecycle/outbox.d.ts +0 -6
  98. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  99. package/lib/opLifecycle/outbox.js +2 -9
  100. package/lib/opLifecycle/outbox.js.map +1 -1
  101. package/lib/packageVersion.d.ts +1 -1
  102. package/lib/packageVersion.js +1 -1
  103. package/lib/packageVersion.js.map +1 -1
  104. package/lib/pendingStateManager.d.ts +7 -3
  105. package/lib/pendingStateManager.d.ts.map +1 -1
  106. package/lib/pendingStateManager.js +19 -7
  107. package/lib/pendingStateManager.js.map +1 -1
  108. package/lib/public.d.ts +1 -1
  109. package/lib/runtimeLayerCompatState.d.ts +1 -1
  110. package/lib/summary/documentSchema.d.ts +9 -3
  111. package/lib/summary/documentSchema.d.ts.map +1 -1
  112. package/lib/summary/documentSchema.js +19 -3
  113. package/lib/summary/documentSchema.js.map +1 -1
  114. package/lib/summary/orderedClientElection.js +2 -2
  115. package/lib/summary/orderedClientElection.js.map +1 -1
  116. package/lib/summary/summaryManager.d.ts +1 -0
  117. package/lib/summary/summaryManager.d.ts.map +1 -1
  118. package/lib/summary/summaryManager.js +9 -0
  119. package/lib/summary/summaryManager.js.map +1 -1
  120. package/package.json +27 -23
  121. package/src/containerCompatibility.ts +2 -0
  122. package/src/containerRuntime.ts +144 -66
  123. package/src/gc/garbageCollection.ts +4 -9
  124. package/src/gc/gcDefinitions.ts +4 -0
  125. package/src/index.ts +1 -0
  126. package/src/opLifecycle/batchManager.ts +2 -1
  127. package/src/opLifecycle/index.ts +1 -0
  128. package/src/opLifecycle/opGroupingManager.ts +11 -1
  129. package/src/opLifecycle/opSerialization.ts +14 -12
  130. package/src/opLifecycle/outbox.ts +2 -17
  131. package/src/packageVersion.ts +1 -1
  132. package/src/pendingStateManager.ts +27 -11
  133. package/src/summary/documentSchema.ts +25 -2
  134. package/src/summary/orderedClientElection.ts +2 -2
  135. 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,28 @@ 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
+ // During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
699
+ // Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
700
+ this.submitIdAllocationOpIfNeeded({ staged: false });
701
+ const batchInfos = discardOrCommit();
702
+ event.reportProgress({
703
+ details: {
704
+ autoFlushThreshold: this.stagingModeAutoFlushThreshold,
705
+ batches: batchInfos.length,
706
+ batchesAtOrOverThreshold: batchInfos.filter((b) => b.length >= this.stagingModeAutoFlushThreshold).length,
707
+ },
708
+ });
709
+ this.channelCollection.notifyStagingMode(false);
710
+ });
666
711
  }
667
712
  catch (error) {
668
713
  const normalizedError = (0, internal_8.normalizeError)(error);
@@ -673,21 +718,22 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
673
718
  const stageControls = {
674
719
  discardChanges: () => exitStagingMode(() => {
675
720
  // Pop all staged batches from the PSM and roll them back in LIFO order
676
- this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
721
+ const batchInfos = this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
677
722
  this.rollbackStagedChange(runtimeOp, localOpMetadata);
678
723
  });
679
724
  this.updateDocumentDirtyState();
680
- }),
725
+ return batchInfos;
726
+ }, "discard"),
681
727
  commitChanges: (options) => {
682
728
  const { squash } = { ...defaultStagingCommitOptions, ...options };
683
729
  exitStagingMode(() => {
684
730
  // Replay all staged batches in typical FIFO order.
685
731
  // We'll be out of staging mode so they'll be sent to the service finally.
686
- this.pendingStateManager.replayPendingStates({
732
+ return this.pendingStateManager.replayPendingStates({
687
733
  committingStagedBatches: true,
688
734
  squash,
689
735
  });
690
- });
736
+ }, "commit");
691
737
  },
692
738
  };
693
739
  this.stageControls = stageControls;
@@ -870,14 +916,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
870
916
  ? this.getConnectionState() === internal_1.ConnectionState.Connected ||
871
917
  this.getConnectionState() === internal_1.ConnectionState.CatchingUp
872
918
  : 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
919
  this.telemetryDocumentId = metadata?.telemetryDocumentId ?? (0, uuid_1.v4)();
882
920
  const opGroupingManager = new index_js_3.OpGroupingManager({
883
921
  groupedBatchingEnabled: this.groupedBatchingEnabled,
@@ -925,6 +963,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
925
963
  this.closeFn(error);
926
964
  throw error;
927
965
  }
966
+ this.stagingModeAutoFlushThreshold =
967
+ this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
968
+ runtimeOptions.stagingModeAutoFlushThreshold ??
969
+ defaultStagingModeAutoFlushThreshold;
928
970
  this.batchIdTrackingEnabled =
929
971
  this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
930
972
  this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
@@ -1034,9 +1076,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1034
1076
  this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
1035
1077
  this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0, internal_8.createChildLogger)({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
1036
1078
  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
1079
  this.outbox = new index_js_3.Outbox({
1041
1080
  shouldSend: () => this.shouldSendOps(),
1042
1081
  pendingStateManager: this.pendingStateManager,
@@ -1047,8 +1086,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1047
1086
  config: {
1048
1087
  compressionOptions,
1049
1088
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1050
- // If we disable flush before process, we must be ready to flush partial batches
1051
- flushPartialBatches: this.skipSafetyFlushDuringProcessStack,
1052
1089
  },
1053
1090
  logger: this.mc.logger,
1054
1091
  groupingManager: opGroupingManager,
@@ -1092,14 +1129,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1092
1129
  // We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
1093
1130
  this.lastEmittedDirty = this.computeCurrentDirtyState();
1094
1131
  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
- }
1132
+ // Reference Sequence Number may have just changed, and it must be consistent across a batch,
1133
+ // so we should flush now to clear the way for the next ops.
1134
+ // NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
1135
+ // But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
1136
+ // (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
1137
+ this.deltaManager.on("op", () => this.flush());
1103
1138
  // logging hardware telemetry
1104
1139
  this.baseLogger.send({
1105
1140
  category: "generic",
@@ -1113,7 +1148,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1113
1148
  summaryNumber: loadSummaryNumber,
1114
1149
  summaryFormatVersion: metadata?.summaryFormatVersion,
1115
1150
  disableIsolatedChannels: metadata?.disableIsolatedChannels,
1151
+ // This is useful even for interactive clients since they track unreferenced nodes and log errors.
1116
1152
  gcVersion: metadata?.gcFeature,
1153
+ gcConfigs: this.garbageCollector.serializedConfigs,
1117
1154
  options: JSON.stringify(runtimeOptions),
1118
1155
  idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
1119
1156
  idCompressorMode: this.sessionSchema.idCompressorMode,
@@ -1121,7 +1158,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1121
1158
  featureGates: JSON.stringify({
1122
1159
  ...featureGatesForTelemetry,
1123
1160
  closeSummarizerDelayOverride,
1124
- disableFlushBeforeProcess: this.skipSafetyFlushDuringProcessStack,
1125
1161
  }),
1126
1162
  telemetryDocumentId: this.telemetryDocumentId,
1127
1163
  groupedBatchingEnabled: this.groupedBatchingEnabled,
@@ -1578,12 +1614,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1578
1614
  (0, internal_2.assert)(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
1579
1615
  this.emitDirtyDocumentEvent = false;
1580
1616
  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();
1617
+ // Any ID Allocation ops that failed to submit need to have their ranges included
1618
+ // in the next allocation op. Reset the compressor's unfinalized range cursor so that the next
1619
+ // call to takeNextCreationRange (during replay) will include those unfinalized ranges.
1620
+ this._idCompressor?.resetUnfinalizedCreationRange();
1587
1621
  // replay the ops
1588
1622
  this.pendingStateManager.replayPendingStates();
1589
1623
  }
@@ -1804,10 +1838,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1804
1838
  // spread operator above ensure we make a shallow copy of message, as the processing flow will modify it.
1805
1839
  // There might be multiple container instances receiving the same message.
1806
1840
  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
- }
1841
+ // Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
1842
+ this.flush();
1811
1843
  this.ensureNoDataModelChanges(() => {
1812
1844
  this.processInboundMessageOrBatch(messageCopy, local);
1813
1845
  });
@@ -3038,6 +3070,17 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3038
3070
  this.updateDocumentDirtyState();
3039
3071
  }
3040
3072
  scheduleFlush() {
3073
+ // During staging mode, suppress automatic flush scheduling until the main batch
3074
+ // reaches or exceeds the threshold.
3075
+ // Incoming ops still break the batch via direct this.flush() calls elsewhere
3076
+ // (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
3077
+ // exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
3078
+ // Additionally, outbox.maybeFlushPartialBatch() (called on every submit) detects
3079
+ // sequence number changes and throws if unexpected changes are detected.
3080
+ if (this.inStagingMode &&
3081
+ this.outbox.mainBatchMessageCount < this.stagingModeAutoFlushThreshold) {
3082
+ return;
3083
+ }
3041
3084
  if (this.flushScheduled) {
3042
3085
  return;
3043
3086
  }
@@ -3332,7 +3375,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3332
3375
  return internal_8.PerformanceEvent.timedExec(this.mc.logger, {
3333
3376
  eventName: "getPendingLocalState",
3334
3377
  }, (event) => {
3335
- const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3378
+ const { pending } = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3336
3379
  const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
3337
3380
  const pendingIdCompressorState = this._idCompressor?.serialize(true);
3338
3381
  const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();