@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723

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 (168) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/batchManager.d.ts +11 -6
  3. package/dist/batchManager.d.ts.map +1 -1
  4. package/dist/batchManager.js +23 -13
  5. package/dist/batchManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +74 -20
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +190 -137
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +50 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +36 -19
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +207 -121
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  28. package/dist/gcSweepReadyUsageDetection.js +3 -12
  29. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  30. package/dist/index.d.ts +4 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/opCompressor.d.ts +18 -0
  35. package/dist/opCompressor.d.ts.map +1 -0
  36. package/dist/opCompressor.js +50 -0
  37. package/dist/opCompressor.js.map +1 -0
  38. package/dist/opDecompressor.d.ts +20 -0
  39. package/dist/opDecompressor.d.ts.map +1 -0
  40. package/dist/opDecompressor.js +72 -0
  41. package/dist/opDecompressor.js.map +1 -0
  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 +6 -26
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +42 -62
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/runningSummarizer.d.ts +3 -2
  50. package/dist/runningSummarizer.d.ts.map +1 -1
  51. package/dist/runningSummarizer.js +10 -3
  52. package/dist/runningSummarizer.js.map +1 -1
  53. package/dist/scheduleManager.js.map +1 -1
  54. package/dist/summarizer.js +7 -2
  55. package/dist/summarizer.js.map +1 -1
  56. package/dist/summarizerClientElection.js +1 -1
  57. package/dist/summarizerClientElection.js.map +1 -1
  58. package/dist/summarizerHeuristics.d.ts.map +1 -1
  59. package/dist/summarizerHeuristics.js +0 -3
  60. package/dist/summarizerHeuristics.js.map +1 -1
  61. package/dist/summarizerTypes.d.ts +19 -2
  62. package/dist/summarizerTypes.d.ts.map +1 -1
  63. package/dist/summarizerTypes.js.map +1 -1
  64. package/dist/summaryFormat.d.ts +4 -2
  65. package/dist/summaryFormat.d.ts.map +1 -1
  66. package/dist/summaryFormat.js.map +1 -1
  67. package/dist/summaryGenerator.d.ts.map +1 -1
  68. package/dist/summaryGenerator.js +3 -2
  69. package/dist/summaryGenerator.js.map +1 -1
  70. package/dist/summaryManager.d.ts.map +1 -1
  71. package/dist/summaryManager.js +10 -6
  72. package/dist/summaryManager.js.map +1 -1
  73. package/garbageCollection.md +27 -22
  74. package/lib/batchManager.d.ts +11 -6
  75. package/lib/batchManager.d.ts.map +1 -1
  76. package/lib/batchManager.js +23 -13
  77. package/lib/batchManager.js.map +1 -1
  78. package/lib/containerRuntime.d.ts +74 -20
  79. package/lib/containerRuntime.d.ts.map +1 -1
  80. package/lib/containerRuntime.js +189 -136
  81. package/lib/containerRuntime.js.map +1 -1
  82. package/lib/dataStore.d.ts.map +1 -1
  83. package/lib/dataStore.js +6 -0
  84. package/lib/dataStore.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +14 -21
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js +75 -61
  88. package/lib/dataStoreContext.js.map +1 -1
  89. package/lib/dataStoreContexts.js +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +11 -10
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js +53 -23
  94. package/lib/dataStores.js.map +1 -1
  95. package/lib/garbageCollection.d.ts +36 -19
  96. package/lib/garbageCollection.d.ts.map +1 -1
  97. package/lib/garbageCollection.js +207 -121
  98. package/lib/garbageCollection.js.map +1 -1
  99. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  100. package/lib/gcSweepReadyUsageDetection.js +3 -12
  101. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  102. package/lib/index.d.ts +4 -6
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +2 -4
  105. package/lib/index.js.map +1 -1
  106. package/lib/opCompressor.d.ts +18 -0
  107. package/lib/opCompressor.d.ts.map +1 -0
  108. package/lib/opCompressor.js +46 -0
  109. package/lib/opCompressor.js.map +1 -0
  110. package/lib/opDecompressor.d.ts +20 -0
  111. package/lib/opDecompressor.d.ts.map +1 -0
  112. package/lib/opDecompressor.js +68 -0
  113. package/lib/opDecompressor.js.map +1 -0
  114. package/lib/packageVersion.d.ts +1 -1
  115. package/lib/packageVersion.js +1 -1
  116. package/lib/packageVersion.js.map +1 -1
  117. package/lib/pendingStateManager.d.ts +6 -26
  118. package/lib/pendingStateManager.d.ts.map +1 -1
  119. package/lib/pendingStateManager.js +42 -62
  120. package/lib/pendingStateManager.js.map +1 -1
  121. package/lib/runningSummarizer.d.ts +3 -2
  122. package/lib/runningSummarizer.d.ts.map +1 -1
  123. package/lib/runningSummarizer.js +10 -3
  124. package/lib/runningSummarizer.js.map +1 -1
  125. package/lib/scheduleManager.js.map +1 -1
  126. package/lib/summarizer.js +7 -2
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerClientElection.js +1 -1
  129. package/lib/summarizerClientElection.js.map +1 -1
  130. package/lib/summarizerHeuristics.d.ts.map +1 -1
  131. package/lib/summarizerHeuristics.js +0 -3
  132. package/lib/summarizerHeuristics.js.map +1 -1
  133. package/lib/summarizerTypes.d.ts +19 -2
  134. package/lib/summarizerTypes.d.ts.map +1 -1
  135. package/lib/summarizerTypes.js.map +1 -1
  136. package/lib/summaryFormat.d.ts +4 -2
  137. package/lib/summaryFormat.d.ts.map +1 -1
  138. package/lib/summaryFormat.js.map +1 -1
  139. package/lib/summaryGenerator.d.ts.map +1 -1
  140. package/lib/summaryGenerator.js +3 -2
  141. package/lib/summaryGenerator.js.map +1 -1
  142. package/lib/summaryManager.d.ts.map +1 -1
  143. package/lib/summaryManager.js +10 -6
  144. package/lib/summaryManager.js.map +1 -1
  145. package/package.json +37 -63
  146. package/prettier.config.cjs +8 -0
  147. package/src/batchManager.ts +32 -15
  148. package/src/containerRuntime.ts +260 -156
  149. package/src/dataStore.ts +13 -1
  150. package/src/dataStoreContext.ts +100 -76
  151. package/src/dataStoreContexts.ts +1 -1
  152. package/src/dataStores.ts +61 -23
  153. package/src/garbageCollection.ts +255 -126
  154. package/src/gcSweepReadyUsageDetection.ts +2 -10
  155. package/src/index.ts +4 -4
  156. package/src/opCompressor.ts +59 -0
  157. package/src/opDecompressor.ts +82 -0
  158. package/src/packageVersion.ts +1 -1
  159. package/src/pendingStateManager.ts +57 -96
  160. package/src/runningSummarizer.ts +11 -3
  161. package/src/scheduleManager.ts +1 -0
  162. package/src/summarizer.ts +6 -6
  163. package/src/summarizerClientElection.ts +1 -1
  164. package/src/summarizerHeuristics.ts +0 -3
  165. package/src/summarizerTypes.ts +20 -7
  166. package/src/summaryFormat.ts +4 -2
  167. package/src/summaryGenerator.ts +3 -2
  168. package/src/summaryManager.ts +18 -7
@@ -147,6 +147,7 @@ import {
147
147
  ISummarizerInternalsProvider,
148
148
  ISummarizerOptions,
149
149
  ISummarizerRuntime,
150
+ IRefreshSummaryAckOptions,
150
151
  } from "./summarizerTypes";
151
152
  import { formExponentialFn, Throttler } from "./throttler";
152
153
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
@@ -166,6 +167,7 @@ import {
166
167
  import { BindBatchTracker } from "./batchTracker";
167
168
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
168
169
  import { ScheduleManager } from "./scheduleManager";
170
+ import { OpDecompressor } from "./opDecompressor";
169
171
 
170
172
  export enum ContainerMessageType {
171
173
  // An op to be delivered to store
@@ -209,6 +211,7 @@ export interface ISummaryBaseConfiguration {
209
211
  initialSummarizerDelayMs: number;
210
212
 
211
213
  /**
214
+ * @deprecated
212
215
  * Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
213
216
  * This defaults to false (disabled) and must be explicitly set to true to enable.
214
217
  */
@@ -229,11 +232,6 @@ export interface ISummaryBaseConfiguration {
229
232
 
230
233
  export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfiguration {
231
234
  state: "enabled";
232
- /**
233
- * @deprecated Please move all implementations to {@link ISummaryConfigurationHeuristics.minIdleTime} and
234
- * {@link ISummaryConfigurationHeuristics.maxIdleTime} instead.
235
- */
236
- idleTime?: number;
237
235
  /**
238
236
  * Defines the maximum allowed time, since the last received Ack, before running the summary
239
237
  * with reason maxTime.
@@ -279,6 +277,17 @@ export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfigurati
279
277
  * For example: (multiplier) * (number of non-runtime ops) = weighted number of non-runtime ops
280
278
  */
281
279
  nonRuntimeOpWeight: number;
280
+
281
+ /**
282
+ * Number of ops since last summary needed before a non-runtime op can trigger running summary heuristics.
283
+ *
284
+ * Note: Any runtime ops sent before the threshold is reached will trigger heuristics normally.
285
+ * This threshold ONLY applies to non-runtime ops triggering summaries.
286
+ *
287
+ * For example: Say the threshold is 20. Sending 19 non-runtime ops will not trigger any heuristic checks.
288
+ * Sending the 20th non-runtime op will trigger the heuristic checks for summarizing.
289
+ */
290
+ nonRuntimeHeuristicThreshold?: number;
282
291
  }
283
292
 
284
293
  export interface ISummaryConfigurationDisableSummarizer {
@@ -318,6 +327,8 @@ export const DefaultSummaryConfiguration: ISummaryConfiguration = {
318
327
  nonRuntimeOpWeight: 0.1,
319
328
 
320
329
  runtimeOpWeight: 1.0,
330
+
331
+ nonRuntimeHeuristicThreshold: 20,
321
332
  };
322
333
 
323
334
  export interface IGCRuntimeOptions {
@@ -416,6 +427,22 @@ export interface ISummaryRuntimeOptions {
416
427
  summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
417
428
  }
418
429
 
430
+ /**
431
+ * Options for op compression.
432
+ * @experimental - Not ready for use
433
+ */
434
+ export interface ICompressionRuntimeOptions {
435
+ /**
436
+ * The minimum size the batch's payload must exceed before the batch's contents will be compressed.
437
+ */
438
+ readonly minimumBatchSizeInBytes: number;
439
+
440
+ /**
441
+ * The compression algorithm that will be used to compress the op.
442
+ */
443
+ readonly compressionAlgorithm: CompressionAlgorithms;
444
+ }
445
+
419
446
  /**
420
447
  * Options for container runtime.
421
448
  */
@@ -442,6 +469,22 @@ export interface IContainerRuntimeOptions {
442
469
  * Save enough runtime state to be able to serialize upon request and load to the same state in a new container.
443
470
  */
444
471
  readonly enableOfflineLoad?: boolean;
472
+ /**
473
+ * Enables the runtime to compress ops. Compression is disabled when undefined.
474
+ * @experimental Not ready for use.
475
+ */
476
+ readonly compressionOptions?: ICompressionRuntimeOptions;
477
+ /**
478
+ * If specified, when in FlushMode.TurnBased, if the size of the ops between JS turns exceeds this value,
479
+ * an error will be thrown and the container will close.
480
+ *
481
+ * If unspecified, the limit is 950 * 1024.
482
+ *
483
+ * 'Infinity' will disable any limit.
484
+ *
485
+ * @experimental This config should be driven by the connection with the service and will be moved in the future.
486
+ */
487
+ readonly maxBatchSizeInBytes?: number;
445
488
  }
446
489
 
447
490
  /**
@@ -467,6 +510,13 @@ export enum RuntimeHeaders {
467
510
  viaHandle = "viaHandle",
468
511
  }
469
512
 
513
+ /**
514
+ * Available compression algorithms for op compression.
515
+ */
516
+ export enum CompressionAlgorithms {
517
+ lz4 = "lz4",
518
+ }
519
+
470
520
  /**
471
521
  * @deprecated
472
522
  * Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
@@ -513,6 +563,12 @@ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconn
513
563
 
514
564
  const defaultFlushMode = FlushMode.TurnBased;
515
565
 
566
+ // The actual limit is 1Mb (socket.io and Kafka limits)
567
+ // We can't estimate it fully, as we
568
+ // - do not know what properties relay service will add
569
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
570
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
571
+
516
572
  /**
517
573
  * @deprecated - use ContainerRuntimeMessage instead
518
574
  */
@@ -530,16 +586,13 @@ export enum RuntimeMessage {
530
586
  * @deprecated - please use version in driver-utils
531
587
  */
532
588
  export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
533
- if ((Object.values(RuntimeMessage) as string[]).includes(message.type)) {
534
- return true;
535
- }
536
- return false;
589
+ return (Object.values(RuntimeMessage) as string[]).includes(message.type);
537
590
  }
538
591
 
539
592
  /**
540
593
  * Unpacks runtime messages
541
594
  *
542
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
595
+ * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
543
596
  * @param message - message (as it observed in storage / service)
544
597
  * @returns unpacked runtime message
545
598
  *
@@ -553,7 +606,6 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
553
606
  } else {
554
607
  // new format
555
608
  const innerContents = message.contents as ContainerRuntimeMessage;
556
- assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
557
609
  message.type = innerContents.type;
558
610
  message.contents = innerContents.contents;
559
611
  }
@@ -635,6 +687,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
635
687
  loadSequenceNumberVerification = "close",
636
688
  flushMode = defaultFlushMode,
637
689
  enableOfflineLoad = false,
690
+ compressionOptions = { minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
691
+ compressionAlgorithm: CompressionAlgorithms.lz4 },
692
+ maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
638
693
  } = runtimeOptions;
639
694
 
640
695
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
@@ -710,6 +765,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
710
765
  loadSequenceNumberVerification,
711
766
  flushMode,
712
767
  enableOfflineLoad,
768
+ compressionOptions,
769
+ maxBatchSizeInBytes,
713
770
  },
714
771
  containerScope,
715
772
  logger,
@@ -725,7 +782,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
725
782
  pendingRuntimeState.savedOps = [];
726
783
  }
727
784
 
728
- await runtime.getSnapshotBlobs();
785
+ // Initialize the base state of the runtime before it's returned.
786
+ await runtime.initializeBaseState();
729
787
 
730
788
  return runtime;
731
789
  }
@@ -787,6 +845,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
787
845
 
788
846
  // internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
789
847
  private readonly mc: MonitoringContext;
848
+
849
+ private readonly opDecompressor: OpDecompressor = new OpDecompressor();
850
+
790
851
  private readonly summarizerClientElection?: SummarizerClientElection;
791
852
  /**
792
853
  * summaryManager will only be created if this client is permitted to spawn a summarizing client
@@ -802,8 +863,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
802
863
  private readonly defaultMaxConsecutiveReconnects = 7;
803
864
 
804
865
  private _orderSequentiallyCalls: number = 0;
805
- private _flushMode: FlushMode;
806
- private flushTrigger = false;
866
+ private readonly _flushMode: FlushMode;
867
+ private flushMicroTaskExists = false;
807
868
 
808
869
  private _connected: boolean;
809
870
 
@@ -851,13 +912,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
851
912
  private readonly scheduleManager: ScheduleManager;
852
913
  private readonly blobManager: BlobManager;
853
914
  private readonly pendingStateManager: PendingStateManager;
854
-
855
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
856
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
857
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
858
- // payloads. That number represents final (compressed) bits (once compression is implemented).
859
- private readonly pendingAttachBatch = new BatchManager(64 * 1024);
860
- private readonly pendingBatch = new BatchManager();
915
+ private readonly pendingAttachBatch: BatchManager;
916
+ private readonly pendingBatch: BatchManager;
861
917
 
862
918
  private readonly garbageCollector: IGarbageCollector;
863
919
 
@@ -965,6 +1021,27 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
965
1021
  },
966
1022
  ) {
967
1023
  super();
1024
+
1025
+ let loadSummaryNumber: number;
1026
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
1027
+ // get the values from the metadata blob.
1028
+ if (existing) {
1029
+ this.createContainerMetadata = {
1030
+ createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
1031
+ createContainerTimestamp: metadata?.createContainerTimestamp,
1032
+ };
1033
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
1034
+ // the count is reset to 0.
1035
+ loadSummaryNumber = metadata?.summaryNumber ?? 0;
1036
+ } else {
1037
+ this.createContainerMetadata = {
1038
+ createContainerRuntimeVersion: pkgVersion,
1039
+ createContainerTimestamp: Date.now(),
1040
+ };
1041
+ loadSummaryNumber = 0;
1042
+ }
1043
+ this.nextSummaryNumber = loadSummaryNumber + 1;
1044
+
968
1045
  this.messageAtLastSummary = metadata?.message;
969
1046
 
970
1047
  this._connected = this.context.connected;
@@ -990,9 +1067,32 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
990
1067
 
991
1068
  this._flushMode = runtimeOptions.flushMode;
992
1069
 
1070
+ // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
1071
+ // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
1072
+ // latency of processing a batch.
1073
+ // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
1074
+ // payloads. That number represents final (compressed) bits (once compression is implemented).
1075
+ this.pendingAttachBatch = new BatchManager(this.mc.logger, {
1076
+ hardLimit: runtimeOptions.maxBatchSizeInBytes,
1077
+ softLimit: 64 * 1024,
1078
+ compressionOptions: runtimeOptions.compressionOptions
1079
+ });
1080
+ this.pendingBatch = new BatchManager(this.mc.logger, {
1081
+ hardLimit: runtimeOptions.maxBatchSizeInBytes,
1082
+ compressionOptions: runtimeOptions.compressionOptions
1083
+ });
1084
+
993
1085
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
994
1086
  const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
995
1087
 
1088
+ const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1089
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
1090
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
1091
+ // which dictates the value is either undefined or exactly 5 days in ms.
1092
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
1093
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
1094
+ }
1095
+
996
1096
  this.garbageCollector = GarbageCollector.create({
997
1097
  runtime: this,
998
1098
  gcOptions: this.runtimeOptions.gcOptions,
@@ -1000,6 +1100,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1000
1100
  baseLogger: this.mc.logger,
1001
1101
  existing,
1002
1102
  metadata,
1103
+ createContainerMetadata: this.createContainerMetadata,
1003
1104
  isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
1004
1105
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1005
1106
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
@@ -1031,7 +1132,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1031
1132
  );
1032
1133
 
1033
1134
  if (baseSnapshot) {
1034
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
1135
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
1035
1136
  }
1036
1137
 
1037
1138
  this.dataStores = new DataStores(
@@ -1060,7 +1161,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1060
1161
  packagePath,
1061
1162
  ),
1062
1163
  new Map<string, string>(dataStoreAliasMap),
1063
- this.garbageCollector.writeDataAtRoot,
1064
1164
  );
1065
1165
 
1066
1166
  this.blobManager = new BlobManager(
@@ -1093,11 +1193,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1093
1193
  close: this.closeFn,
1094
1194
  connected: () => this.connected,
1095
1195
  flush: this.flush.bind(this),
1096
- flushMode: () => this.flushMode,
1097
1196
  reSubmit: this.reSubmit.bind(this),
1098
- setFlushMode: (mode) => this.setFlushMode(mode),
1197
+ rollback: this.rollback.bind(this),
1198
+ orderSequentially: this.orderSequentially.bind(this),
1099
1199
  },
1100
- this._flushMode,
1101
1200
  pendingRuntimeState?.pending);
1102
1201
 
1103
1202
  this.context.quorum.on("removeMember", (clientId: string) => {
@@ -1150,7 +1249,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1150
1249
  // if summaries are enabled and we are not the summarizer client.
1151
1250
  const defaultAction = () => {
1152
1251
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
1153
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
1252
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1154
1253
  // unregister default to no log on every op after falling behind
1155
1254
  // and register summary ack handler to re-register this handler
1156
1255
  // after successful summary
@@ -1217,26 +1316,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1217
1316
  ...getDeviceSpec(),
1218
1317
  });
1219
1318
 
1220
- let loadSummaryNumber: number;
1221
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
1222
- // get the values from the metadata blob.
1223
- if (existing) {
1224
- this.createContainerMetadata = {
1225
- createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
1226
- createContainerTimestamp: metadata?.createContainerTimestamp,
1227
- };
1228
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
1229
- // the count is reset to 0.
1230
- loadSummaryNumber = metadata?.summaryNumber ?? 0;
1231
- } else {
1232
- this.createContainerMetadata = {
1233
- createContainerRuntimeVersion: pkgVersion,
1234
- createContainerTimestamp: Date.now(),
1235
- };
1236
- loadSummaryNumber = 0;
1237
- }
1238
- this.nextSummaryNumber = loadSummaryNumber + 1;
1239
-
1240
1319
  this.logger.sendTelemetryEvent({
1241
1320
  eventName: "ContainerLoadStats",
1242
1321
  ...this.createContainerMetadata,
@@ -1251,6 +1330,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1251
1330
  BindBatchTracker(this, this.logger);
1252
1331
  }
1253
1332
 
1333
+ /**
1334
+ * Initializes the state from the base snapshot this container runtime loaded from.
1335
+ */
1336
+ private async initializeBaseState(): Promise<void> {
1337
+ await this.initializeBaseSnapshotBlobs();
1338
+ await this.garbageCollector.initializeBaseState();
1339
+ }
1340
+
1254
1341
  public dispose(error?: Error): void {
1255
1342
  if (this._disposed) {
1256
1343
  return;
@@ -1359,10 +1446,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1359
1446
  const wait = typeof request.headers?.[RuntimeHeaders.wait] === "boolean"
1360
1447
  ? request.headers?.[RuntimeHeaders.wait]
1361
1448
  : true;
1449
+ const viaHandle = typeof request.headers?.[RuntimeHeaders.viaHandle] === "boolean"
1450
+ ? request.headers?.[RuntimeHeaders.viaHandle]
1451
+ : false;
1362
1452
 
1363
1453
  await this.dataStores.waitIfPendingAlias(id);
1364
1454
  const internalId = this.internalId(id);
1365
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
1455
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
1366
1456
 
1367
1457
  /**
1368
1458
  * If GC should run and this an external app request with "externalRequest" header, we need to return
@@ -1441,11 +1531,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1441
1531
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
1442
1532
  }
1443
1533
 
1444
- if (this.garbageCollector.writeDataAtRoot) {
1445
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1446
- if (gcSummary !== undefined) {
1447
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1448
- }
1534
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1535
+ if (gcSummary !== undefined) {
1536
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1449
1537
  }
1450
1538
  }
1451
1539
 
@@ -1630,6 +1718,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1630
1718
  message.contents = JSON.parse(message.contents);
1631
1719
  }
1632
1720
 
1721
+ message = this.opDecompressor.processMessage(message);
1722
+
1633
1723
  // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
1634
1724
  // This format was not shipped to production workflows.
1635
1725
  const runtimeMessage = unpackRuntimeMessage(message);
@@ -1767,34 +1857,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1767
1857
  private async getRootDataStoreChannel(id: string, wait = true): Promise<IFluidDataStoreChannel> {
1768
1858
  await this.dataStores.waitIfPendingAlias(id);
1769
1859
  const internalId = this.internalId(id);
1770
- const context = await this.dataStores.getDataStore(internalId, wait);
1860
+ const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1771
1861
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1772
1862
  return context.realize();
1773
1863
  }
1774
1864
 
1775
- public setFlushMode(mode: FlushMode): void {
1776
- if (mode === this._flushMode) {
1777
- return;
1778
- }
1779
-
1780
- this.mc.logger.sendTelemetryEvent({
1781
- eventName: "FlushMode Updated",
1782
- old: this._flushMode,
1783
- new: mode,
1784
- });
1785
-
1786
- // Flush any pending batches if switching to immediate
1787
- if (mode === FlushMode.Immediate) {
1788
- this.flush();
1789
- }
1790
-
1791
- this._flushMode = mode;
1792
-
1793
- // Let the PendingStateManager know that FlushMode has been updated.
1794
- this.pendingStateManager.onFlushModeUpdated(mode);
1795
- }
1796
-
1797
- public flush(): void {
1865
+ /**
1866
+ * Flush the pending ops manually.
1867
+ * This method is expected to be called at the end of a batch.
1868
+ */
1869
+ private flush(): void {
1798
1870
  assert(this._orderSequentiallyCalls === 0,
1799
1871
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1800
1872
 
@@ -1829,15 +1901,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1829
1901
  if (this.canSendOps()) {
1830
1902
  if (this.context.submitBatchFn !== undefined) {
1831
1903
  const batchToSend: IBatchMessage[] = [];
1904
+
1832
1905
  for (const message of batch) {
1833
1906
  batchToSend.push({ contents: message.contents, metadata: message.metadata });
1834
1907
  }
1908
+
1835
1909
  // returns clientSequenceNumber of last message in a batch
1836
1910
  clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1837
1911
  } else {
1838
1912
  // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1839
1913
  // version that has support for batches (submitBatchFn)
1840
1914
  for (const message of batch) {
1915
+ // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
1916
+ if (message.metadata?.compressed) {
1917
+ delete message.metadata.compressed;
1918
+ }
1919
+
1841
1920
  clientSequenceNumber = this.context.submitFn(
1842
1921
  MessageType.Operation,
1843
1922
  message.deserializedContent,
@@ -1871,36 +1950,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1871
1950
  }
1872
1951
 
1873
1952
  public orderSequentially(callback: () => void): void {
1874
- // If flush mode is already TurnBased we are either
1875
- // nested in another orderSequentially, or
1876
- // the app is flushing manually, in which
1877
- // case this invocation doesn't own
1878
- // flushing.
1879
- if (this.flushMode === FlushMode.TurnBased) {
1880
- this.trackOrderSequentiallyCalls(callback);
1881
- return;
1882
- }
1883
-
1884
- const savedFlushMode = this.flushMode;
1885
- this.setFlushMode(FlushMode.TurnBased);
1886
-
1887
- try {
1888
- this.trackOrderSequentiallyCalls(callback);
1889
- this.flush();
1890
- } finally {
1891
- this.setFlushMode(savedFlushMode);
1892
- }
1893
- }
1894
-
1895
- private trackOrderSequentiallyCalls(callback: () => void): void {
1896
1953
  let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
1954
+
1897
1955
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1898
1956
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1899
1957
  // 1. It would not help, as we flush attach ops as they become available.
1900
1958
  // 2. There is no way to undo process of data store creation.
1901
1959
  checkpoint = this.pendingBatch.checkpoint();
1902
1960
  }
1903
-
1904
1961
  try {
1905
1962
  this._orderSequentiallyCalls++;
1906
1963
  callback();
@@ -1931,6 +1988,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1931
1988
  } finally {
1932
1989
  this._orderSequentiallyCalls--;
1933
1990
  }
1991
+
1992
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1993
+ this.flush();
1994
+ }
1934
1995
  }
1935
1996
 
1936
1997
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
@@ -1980,6 +2041,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1980
2041
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1981
2042
  }
1982
2043
 
2044
+ /**
2045
+ * Are we in the middle of batching ops together?
2046
+ */
2047
+ private currentlyBatching() {
2048
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
2049
+ }
2050
+
1983
2051
  public getQuorum(): IQuorumClients {
1984
2052
  return this.context.quorum;
1985
2053
  }
@@ -2192,10 +2260,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2192
2260
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
2193
2261
  * After GC has run, called to notify this container's nodes of routes that are used in it.
2194
2262
  * @param usedRoutes - The routes that are used in all nodes in this Container.
2195
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
2196
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
2197
2263
  */
2198
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
2264
+ public updateUsedRoutes(usedRoutes: string[]) {
2199
2265
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
2200
2266
  // summarizing is required and asserted by the the summarizer node. We are the root and are
2201
2267
  // always referenced, so the used routes is only self-route (empty string).
@@ -2208,15 +2274,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2208
2274
  }
2209
2275
  }
2210
2276
 
2211
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
2277
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2212
2278
  }
2213
2279
 
2214
2280
  /**
2215
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
2216
- * scenarios with accessing deleted content.
2281
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
2282
+ * tombstones.
2217
2283
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
2284
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
2285
+ * are deleted.
2218
2286
  */
2219
- public deleteUnusedRoutes(unusedRoutes: string[]) {
2287
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
2220
2288
  const blobManagerUnusedRoutes: string[] = [];
2221
2289
  const dataStoreUnusedRoutes: string[] = [];
2222
2290
  for (const route of unusedRoutes) {
@@ -2227,8 +2295,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2227
2295
  }
2228
2296
  }
2229
2297
 
2230
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2231
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
2298
+ // Todo: Add tombstone for attachment blobs. For now, we ignore attachment blobs that should be tombstoned.
2299
+ if (!tombstone) {
2300
+ this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2301
+ }
2302
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
2232
2303
  }
2233
2304
 
2234
2305
  /**
@@ -2335,20 +2406,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2335
2406
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2336
2407
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
2337
2408
 
2338
- if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2339
- // We need to catch up to the latest summary's reference sequence number before pausing.
2340
- await PerformanceEvent.timedExecAsync(
2341
- summaryNumberLogger,
2342
- {
2343
- eventName: "WaitingForSeq",
2344
- lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2345
- targetSequenceNumber: latestSnapshotRefSeq,
2346
- lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2347
- },
2348
- async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2349
- { start: true, end: true, cancel: "error" }, // definitely want start event
2350
- );
2351
- }
2409
+ // We might need to catch up to the latest summary's reference sequence number before pausing.
2410
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq,
2411
+ summaryNumberLogger);
2352
2412
  }
2353
2413
 
2354
2414
  try {
@@ -2680,8 +2740,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2680
2740
  // issue than sending.
2681
2741
  // Please note that this does not change file format, so it can be disabled in the future if this
2682
2742
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
2683
- if (type === ContainerMessageType.Attach &&
2684
- this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
2743
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
2744
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
2685
2745
  if (!this.pendingAttachBatch.push(message)) {
2686
2746
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
2687
2747
  // when queue is not empty.
@@ -2692,9 +2752,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2692
2752
  "BatchTooLarge",
2693
2753
  /* error */ undefined,
2694
2754
  {
2695
- opSize: message.contents.length,
2755
+ opSize: (message.contents?.length) ?? 0,
2696
2756
  count: this.pendingAttachBatch.length,
2697
- limit: this.pendingAttachBatch.limit,
2757
+ limit: this.pendingAttachBatch.options.hardLimit,
2698
2758
  });
2699
2759
  }
2700
2760
  }
@@ -2704,23 +2764,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2704
2764
  "BatchTooLarge",
2705
2765
  /* error */ undefined,
2706
2766
  {
2707
- opSize: message.contents.length,
2767
+ opSize: (message.contents?.length) ?? 0,
2708
2768
  count: this.pendingBatch.length,
2709
- limit: this.pendingBatch.limit,
2769
+ limit: this.pendingBatch.options.hardLimit,
2710
2770
  });
2711
2771
  }
2712
- }
2713
-
2714
- if (this._flushMode !== FlushMode.TurnBased) {
2715
- this.flush();
2716
- } else if (!this.flushTrigger) {
2717
- this.flushTrigger = true;
2718
- // Queue a microtask to detect the end of the turn and force a flush.
2719
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
2720
- Promise.resolve().then(() => {
2721
- this.flushTrigger = false;
2772
+ if (!this.currentlyBatching()) {
2722
2773
  this.flush();
2723
- });
2774
+ } else if (!this.flushMicroTaskExists) {
2775
+ this.flushMicroTaskExists = true;
2776
+ // Queue a microtask to detect the end of the turn and force a flush.
2777
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
2778
+ Promise.resolve().then(() => {
2779
+ this.flushMicroTaskExists = false;
2780
+ this.flush();
2781
+ });
2782
+ }
2724
2783
  }
2725
2784
  } catch (error) {
2726
2785
  this.closeFn(error as GenericError);
@@ -2809,28 +2868,61 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2809
2868
  }
2810
2869
  }
2811
2870
 
2812
- /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2813
- public async refreshLatestSummaryAck(
2814
- proposalHandle: string | undefined,
2815
- ackHandle: string,
2816
- summaryRefSeq: number,
2871
+ private async waitForDeltaManagerToCatchup(
2872
+ latestSnapshotRefSeq: number,
2817
2873
  summaryLogger: ITelemetryLogger,
2818
- ) {
2874
+ ): Promise<void> {
2875
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2876
+ // We need to catch up to the latest summary's reference sequence number before proceeding.
2877
+ await PerformanceEvent.timedExecAsync(
2878
+ summaryLogger,
2879
+ {
2880
+ eventName: "WaitingForSeq",
2881
+ lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2882
+ targetSequenceNumber: latestSnapshotRefSeq,
2883
+ lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2884
+ },
2885
+ async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2886
+ { start: true, end: true, cancel: "error" }, // definitely want start event
2887
+ );
2888
+ }
2889
+ }
2890
+
2891
+ /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2892
+ public async refreshLatestSummaryAck(options: IRefreshSummaryAckOptions) {
2893
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
2819
2894
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
2820
2895
  // The call to fetch the snapshot is very expensive and not always needed.
2821
2896
  // It should only be done by the summarizerNode, if required.
2897
+ // When fetching from storage we will always get the latest version and do not use the ackHandle.
2822
2898
  const snapshotTreeFetcher = async () => {
2823
2899
  const fetchResult = await this.fetchSnapshotFromStorage(
2824
- ackHandle,
2900
+ null,
2825
2901
  summaryLogger,
2826
2902
  {
2827
2903
  eventName: "RefreshLatestSummaryGetSnapshot",
2828
2904
  ackHandle,
2829
2905
  summaryRefSeq,
2830
- fetchLatest: false,
2906
+ fetchLatest: true,
2831
2907
  });
2908
+
2909
+ const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2910
+ summaryLogger.sendTelemetryEvent(
2911
+ {
2912
+ eventName: "LatestSummaryRetrieved",
2913
+ ackHandle,
2914
+ lastSequenceNumber: latestSnapshotRefSeq,
2915
+ targetSequenceNumber: summaryRefSeq,
2916
+ });
2917
+
2918
+ // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
2919
+ // wait for the delta manager to catch up before refreshing the latest Summary.
2920
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq,
2921
+ summaryLogger);
2922
+
2832
2923
  return fetchResult.snapshotTree;
2833
2924
  };
2925
+
2834
2926
  const result = await this.summarizerNode.refreshLatestSummary(
2835
2927
  proposalHandle,
2836
2928
  summaryRefSeq,
@@ -2912,7 +3004,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2912
3004
  }
2913
3005
  }
2914
3006
 
2915
- public async getSnapshotBlobs(): Promise<void> {
3007
+ private async initializeBaseSnapshotBlobs(): Promise<void> {
2916
3008
  if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) ||
2917
3009
  this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
2918
3010
  return;
@@ -2931,6 +3023,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2931
3023
  // to close current batch.
2932
3024
  this.flush();
2933
3025
 
3026
+ if (this._orderSequentiallyCalls !== 0) {
3027
+ throw new UsageError("can't get state during orderSequentially");
3028
+ }
3029
+
2934
3030
  const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
2935
3031
  if (previousPendingState) {
2936
3032
  return {
@@ -3036,6 +3132,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3036
3132
  throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
3037
3133
  }
3038
3134
  }
3135
+ if (configuration.minIdleTime > configuration.maxIdleTime) {
3136
+ throw new UsageError(`"minIdleTime" [${configuration.minIdleTime}] cannot be greater than "maxIdleTime" [${configuration.maxIdleTime}]`);
3137
+ }
3039
3138
  }
3040
3139
  }
3041
3140
 
@@ -3050,11 +3149,16 @@ const waitForSeq = async (
3050
3149
  // TODO: remove cast to any when actual event is determined
3051
3150
  deltaManager.on("closed" as any, reject);
3052
3151
 
3053
- const handleOp = (message: Pick<ISequencedDocumentMessage, "sequenceNumber">) => {
3054
- if (message.sequenceNumber >= targetSeq) {
3055
- resolve();
3056
- deltaManager.off("op", handleOp);
3057
- }
3058
- };
3059
- deltaManager.on("op", handleOp);
3152
+ // If we already reached target sequence number, simply resolve the promise.
3153
+ if (deltaManager.lastSequenceNumber >= targetSeq) {
3154
+ resolve();
3155
+ } else {
3156
+ const handleOp = (message: Pick<ISequencedDocumentMessage, "sequenceNumber">) => {
3157
+ if (message.sequenceNumber >= targetSeq) {
3158
+ resolve();
3159
+ deltaManager.off("op", handleOp);
3160
+ }
3161
+ };
3162
+ deltaManager.on("op", handleOp);
3163
+ }
3060
3164
  });