@fluidframework/container-runtime 2.0.0-internal.1.4.2 → 2.0.0-internal.2.0.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 (107) hide show
  1. package/dist/batchManager.d.ts +2 -3
  2. package/dist/batchManager.d.ts.map +1 -1
  3. package/dist/batchManager.js +3 -8
  4. package/dist/batchManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +43 -16
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +107 -83
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +4 -20
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +17 -47
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/dataStores.d.ts +2 -5
  14. package/dist/dataStores.d.ts.map +1 -1
  15. package/dist/dataStores.js +3 -11
  16. package/dist/dataStores.js.map +1 -1
  17. package/dist/garbageCollection.d.ts +1 -10
  18. package/dist/garbageCollection.d.ts.map +1 -1
  19. package/dist/garbageCollection.js +43 -51
  20. package/dist/garbageCollection.js.map +1 -1
  21. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  22. package/dist/gcSweepReadyUsageDetection.js +3 -12
  23. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  24. package/dist/index.d.ts +3 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/pendingStateManager.d.ts +6 -26
  32. package/dist/pendingStateManager.d.ts.map +1 -1
  33. package/dist/pendingStateManager.js +42 -62
  34. package/dist/pendingStateManager.js.map +1 -1
  35. package/dist/scheduleManager.js.map +1 -1
  36. package/dist/summarizer.js +7 -2
  37. package/dist/summarizer.js.map +1 -1
  38. package/dist/summarizerTypes.d.ts +19 -2
  39. package/dist/summarizerTypes.d.ts.map +1 -1
  40. package/dist/summarizerTypes.js.map +1 -1
  41. package/dist/summaryFormat.d.ts +4 -2
  42. package/dist/summaryFormat.d.ts.map +1 -1
  43. package/dist/summaryFormat.js.map +1 -1
  44. package/dist/summaryManager.d.ts.map +1 -1
  45. package/dist/summaryManager.js +10 -6
  46. package/dist/summaryManager.js.map +1 -1
  47. package/lib/batchManager.d.ts +2 -3
  48. package/lib/batchManager.d.ts.map +1 -1
  49. package/lib/batchManager.js +3 -8
  50. package/lib/batchManager.js.map +1 -1
  51. package/lib/containerRuntime.d.ts +43 -16
  52. package/lib/containerRuntime.d.ts.map +1 -1
  53. package/lib/containerRuntime.js +108 -84
  54. package/lib/containerRuntime.js.map +1 -1
  55. package/lib/dataStoreContext.d.ts +4 -20
  56. package/lib/dataStoreContext.d.ts.map +1 -1
  57. package/lib/dataStoreContext.js +18 -48
  58. package/lib/dataStoreContext.js.map +1 -1
  59. package/lib/dataStores.d.ts +2 -5
  60. package/lib/dataStores.d.ts.map +1 -1
  61. package/lib/dataStores.js +3 -11
  62. package/lib/dataStores.js.map +1 -1
  63. package/lib/garbageCollection.d.ts +1 -10
  64. package/lib/garbageCollection.d.ts.map +1 -1
  65. package/lib/garbageCollection.js +42 -50
  66. package/lib/garbageCollection.js.map +1 -1
  67. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  68. package/lib/gcSweepReadyUsageDetection.js +3 -12
  69. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  70. package/lib/index.d.ts +3 -5
  71. package/lib/index.d.ts.map +1 -1
  72. package/lib/index.js +0 -2
  73. package/lib/index.js.map +1 -1
  74. package/lib/packageVersion.d.ts +1 -1
  75. package/lib/packageVersion.js +1 -1
  76. package/lib/packageVersion.js.map +1 -1
  77. package/lib/pendingStateManager.d.ts +6 -26
  78. package/lib/pendingStateManager.d.ts.map +1 -1
  79. package/lib/pendingStateManager.js +42 -62
  80. package/lib/pendingStateManager.js.map +1 -1
  81. package/lib/scheduleManager.js.map +1 -1
  82. package/lib/summarizer.js +7 -2
  83. package/lib/summarizer.js.map +1 -1
  84. package/lib/summarizerTypes.d.ts +19 -2
  85. package/lib/summarizerTypes.d.ts.map +1 -1
  86. package/lib/summarizerTypes.js.map +1 -1
  87. package/lib/summaryFormat.d.ts +4 -2
  88. package/lib/summaryFormat.d.ts.map +1 -1
  89. package/lib/summaryFormat.js.map +1 -1
  90. package/lib/summaryManager.d.ts.map +1 -1
  91. package/lib/summaryManager.js +10 -6
  92. package/lib/summaryManager.js.map +1 -1
  93. package/package.json +40 -38
  94. package/src/batchManager.ts +7 -11
  95. package/src/containerRuntime.ts +149 -102
  96. package/src/dataStoreContext.ts +20 -62
  97. package/src/dataStores.ts +2 -10
  98. package/src/garbageCollection.ts +45 -55
  99. package/src/gcSweepReadyUsageDetection.ts +2 -10
  100. package/src/index.ts +2 -3
  101. package/src/packageVersion.ts +1 -1
  102. package/src/pendingStateManager.ts +57 -96
  103. package/src/scheduleManager.ts +1 -0
  104. package/src/summarizer.ts +6 -6
  105. package/src/summarizerTypes.ts +20 -7
  106. package/src/summaryFormat.ts +4 -2
  107. package/src/summaryManager.ts +18 -7
@@ -34,6 +34,7 @@ import {
34
34
  Trace,
35
35
  TypedEventEmitter,
36
36
  unreachableCase,
37
+ IsoBuffer,
37
38
  } from "@fluidframework/common-utils";
38
39
  import {
39
40
  ChildLogger,
@@ -107,6 +108,7 @@ import {
107
108
  } from "@fluidframework/runtime-utils";
108
109
  import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
109
110
  import { v4 as uuid } from "uuid";
111
+ import { compress, decompress } from "lz4js";
110
112
  import { ContainerFluidHandleContext } from "./containerHandleContext";
111
113
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
112
114
  import { Summarizer } from "./summarizer";
@@ -147,6 +149,7 @@ import {
147
149
  ISummarizerInternalsProvider,
148
150
  ISummarizerOptions,
149
151
  ISummarizerRuntime,
152
+ IRefreshSummaryAckOptions,
150
153
  } from "./summarizerTypes";
151
154
  import { formExponentialFn, Throttler } from "./throttler";
152
155
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
@@ -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.
@@ -297,8 +295,6 @@ export type ISummaryConfiguration =
297
295
  export const DefaultSummaryConfiguration: ISummaryConfiguration = {
298
296
  state: "enabled",
299
297
 
300
- idleTime: 15 * 1000, // 15 secs.
301
-
302
298
  minIdleTime: 0,
303
299
 
304
300
  maxIdleTime: 30 * 1000, // 30 secs.
@@ -418,6 +414,18 @@ export interface ISummaryRuntimeOptions {
418
414
  summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
419
415
  }
420
416
 
417
+ /**
418
+ * Options for op compression.
419
+ * @experimental - Not ready for use
420
+ */
421
+ export interface ICompressionRuntimeOptions {
422
+ /**
423
+ * The minimum size the content payload must exceed before it is compressed.
424
+ * Compression is disabled if undefined.
425
+ */
426
+ readonly minimumSize?: number;
427
+ }
428
+
421
429
  /**
422
430
  * Options for container runtime.
423
431
  */
@@ -444,6 +452,22 @@ export interface IContainerRuntimeOptions {
444
452
  * Save enough runtime state to be able to serialize upon request and load to the same state in a new container.
445
453
  */
446
454
  readonly enableOfflineLoad?: boolean;
455
+ /**
456
+ * Enables the runtime to compress ops.
457
+ * @experimental Not ready for use.
458
+ */
459
+ readonly compressionOptions?: ICompressionRuntimeOptions;
460
+ /**
461
+ * If specified, when in FlushMode.TurnBased, if the size of the ops between JS turns exceeds this value,
462
+ * an error will be thrown and the container will close.
463
+ *
464
+ * If unspecified, the limit is 950 * 1024.
465
+ *
466
+ * 'Infinity' will disable any limit.
467
+ *
468
+ * @experimental This config should be driven by the connection with the service and will be moved in the future.
469
+ */
470
+ readonly maxBatchSizeInBytes?: number;
447
471
  }
448
472
 
449
473
  /**
@@ -515,6 +539,12 @@ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconn
515
539
 
516
540
  const defaultFlushMode = FlushMode.TurnBased;
517
541
 
542
+ // The actual limit is 1Mb (socket.io and Kafka limits)
543
+ // We can't estimate it fully, as we
544
+ // - do not know what properties relay service will add
545
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
546
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
547
+
518
548
  /**
519
549
  * @deprecated - use ContainerRuntimeMessage instead
520
550
  */
@@ -532,22 +562,28 @@ export enum RuntimeMessage {
532
562
  * @deprecated - please use version in driver-utils
533
563
  */
534
564
  export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
535
- if ((Object.values(RuntimeMessage) as string[]).includes(message.type)) {
536
- return true;
537
- }
538
- return false;
565
+ return (Object.values(RuntimeMessage) as string[]).includes(message.type);
539
566
  }
540
567
 
541
568
  /**
542
569
  * Unpacks runtime messages
543
570
  *
544
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
571
+ * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
545
572
  * @param message - message (as it observed in storage / service)
546
573
  * @returns unpacked runtime message
547
574
  *
548
575
  * @internal
549
576
  */
550
577
  export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
578
+ if (message.metadata?.compressed) {
579
+ const contents = IsoBuffer.from(message.contents.contents, "base64");
580
+ const decompressedMessage = decompress(contents);
581
+ const intoString = new TextDecoder().decode(decompressedMessage);
582
+ const asObj = JSON.parse(intoString);
583
+ message.contents.contents = asObj;
584
+ message.metadata.compressed = false;
585
+ }
586
+
551
587
  if (message.type === MessageType.Operation) {
552
588
  // legacy op format?
553
589
  if (message.contents.address !== undefined && message.contents.type === undefined) {
@@ -637,6 +673,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
637
673
  loadSequenceNumberVerification = "close",
638
674
  flushMode = defaultFlushMode,
639
675
  enableOfflineLoad = false,
676
+ compressionOptions = {},
677
+ maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
640
678
  } = runtimeOptions;
641
679
 
642
680
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
@@ -712,6 +750,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
712
750
  loadSequenceNumberVerification,
713
751
  flushMode,
714
752
  enableOfflineLoad,
753
+ compressionOptions,
754
+ maxBatchSizeInBytes,
715
755
  },
716
756
  containerScope,
717
757
  logger,
@@ -804,8 +844,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
804
844
  private readonly defaultMaxConsecutiveReconnects = 7;
805
845
 
806
846
  private _orderSequentiallyCalls: number = 0;
807
- private _flushMode: FlushMode;
808
- private flushTrigger = false;
847
+ private readonly _flushMode: FlushMode;
848
+ private flushMicroTaskExists = false;
809
849
 
810
850
  private _connected: boolean;
811
851
 
@@ -813,6 +853,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
813
853
  private baseSnapshotBlobs?: ISerializedBaseSnapshotBlobs;
814
854
 
815
855
  private consecutiveReconnects = 0;
856
+ private compressedOpCount = 0;
816
857
 
817
858
  /**
818
859
  * Used to delay transition to "connected" state while we upload
@@ -853,13 +894,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
853
894
  private readonly scheduleManager: ScheduleManager;
854
895
  private readonly blobManager: BlobManager;
855
896
  private readonly pendingStateManager: PendingStateManager;
856
-
857
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
858
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
859
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
860
- // payloads. That number represents final (compressed) bits (once compression is implemented).
861
- private readonly pendingAttachBatch = new BatchManager(64 * 1024);
862
- private readonly pendingBatch = new BatchManager();
897
+ private readonly pendingAttachBatch: BatchManager;
898
+ private readonly pendingBatch: BatchManager;
863
899
 
864
900
  private readonly garbageCollector: IGarbageCollector;
865
901
 
@@ -992,9 +1028,25 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
992
1028
 
993
1029
  this._flushMode = runtimeOptions.flushMode;
994
1030
 
1031
+ // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
1032
+ // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
1033
+ // latency of processing a batch.
1034
+ // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
1035
+ // payloads. That number represents final (compressed) bits (once compression is implemented).
1036
+ this.pendingAttachBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
1037
+ this.pendingBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes);
1038
+
995
1039
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
996
1040
  const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
997
1041
 
1042
+ const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1043
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
1044
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
1045
+ // which dictates the value is either undefined or exactly 5 days in ms.
1046
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
1047
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
1048
+ }
1049
+
998
1050
  this.garbageCollector = GarbageCollector.create({
999
1051
  runtime: this,
1000
1052
  gcOptions: this.runtimeOptions.gcOptions,
@@ -1033,7 +1085,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1033
1085
  );
1034
1086
 
1035
1087
  if (baseSnapshot) {
1036
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
1088
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
1037
1089
  }
1038
1090
 
1039
1091
  this.dataStores = new DataStores(
@@ -1062,7 +1114,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1062
1114
  packagePath,
1063
1115
  ),
1064
1116
  new Map<string, string>(dataStoreAliasMap),
1065
- this.garbageCollector.writeDataAtRoot,
1066
1117
  );
1067
1118
 
1068
1119
  this.blobManager = new BlobManager(
@@ -1095,11 +1146,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1095
1146
  close: this.closeFn,
1096
1147
  connected: () => this.connected,
1097
1148
  flush: this.flush.bind(this),
1098
- flushMode: () => this.flushMode,
1099
1149
  reSubmit: this.reSubmit.bind(this),
1100
- setFlushMode: (mode) => this.setFlushMode(mode),
1150
+ rollback: this.rollback.bind(this),
1151
+ orderSequentially: this.orderSequentially.bind(this),
1101
1152
  },
1102
- this._flushMode,
1103
1153
  pendingRuntimeState?.pending);
1104
1154
 
1105
1155
  this.context.quorum.on("removeMember", (clientId: string) => {
@@ -1152,7 +1202,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1152
1202
  // if summaries are enabled and we are not the summarizer client.
1153
1203
  const defaultAction = () => {
1154
1204
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
1155
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
1205
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1156
1206
  // unregister default to no log on every op after falling behind
1157
1207
  // and register summary ack handler to re-register this handler
1158
1208
  // after successful summary
@@ -1443,11 +1493,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1443
1493
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
1444
1494
  }
1445
1495
 
1446
- if (this.garbageCollector.writeDataAtRoot) {
1447
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1448
- if (gcSummary !== undefined) {
1449
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1450
- }
1496
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1497
+ if (gcSummary !== undefined) {
1498
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1451
1499
  }
1452
1500
  }
1453
1501
 
@@ -1774,29 +1822,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1774
1822
  return context.realize();
1775
1823
  }
1776
1824
 
1777
- public setFlushMode(mode: FlushMode): void {
1778
- if (mode === this._flushMode) {
1779
- return;
1780
- }
1781
-
1782
- this.mc.logger.sendTelemetryEvent({
1783
- eventName: "FlushMode Updated",
1784
- old: this._flushMode,
1785
- new: mode,
1786
- });
1787
-
1788
- // Flush any pending batches if switching to immediate
1789
- if (mode === FlushMode.Immediate) {
1790
- this.flush();
1791
- }
1792
-
1793
- this._flushMode = mode;
1794
-
1795
- // Let the PendingStateManager know that FlushMode has been updated.
1796
- this.pendingStateManager.onFlushModeUpdated(mode);
1797
- }
1798
-
1799
- public flush(): void {
1825
+ /**
1826
+ * Flush the pending ops manually.
1827
+ * This method is expected to be called at the end of a batch.
1828
+ */
1829
+ private flush(): void {
1800
1830
  assert(this._orderSequentiallyCalls === 0,
1801
1831
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1802
1832
 
@@ -1832,7 +1862,38 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1832
1862
  if (this.context.submitBatchFn !== undefined) {
1833
1863
  const batchToSend: IBatchMessage[] = [];
1834
1864
  for (const message of batch) {
1835
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1865
+ let contents = message.contents;
1866
+ let metadata = message.metadata;
1867
+ if (this.runtimeOptions.compressionOptions.minimumSize &&
1868
+ this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
1869
+ this.compressedOpCount++;
1870
+ const copiedMessage = { ...message.deserializedContent };
1871
+
1872
+ const compressionStart = Date.now();
1873
+ const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
1874
+ const compressedContents = compress(contentsAsBuffer);
1875
+ const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
1876
+ const duration = Date.now() - compressionStart;
1877
+
1878
+ if (this.compressedOpCount % 100) {
1879
+ this.mc.logger.sendPerformanceEvent({
1880
+ eventName: "compressedOp",
1881
+ duration,
1882
+ sizeBeforeCompression: message.contents.length,
1883
+ sizeAfterCompression: compressedContent.length,
1884
+ });
1885
+ }
1886
+
1887
+ copiedMessage.contents = compressedContent;
1888
+ const stringifiedContents = JSON.stringify(copiedMessage);
1889
+
1890
+ if (stringifiedContents.length < message.contents.length) {
1891
+ contents = JSON.stringify(copiedMessage);
1892
+ metadata = { ...message.metadata, compressed: true };
1893
+ }
1894
+ }
1895
+
1896
+ batchToSend.push({ contents, metadata });
1836
1897
  }
1837
1898
  // returns clientSequenceNumber of last message in a batch
1838
1899
  clientSequenceNumber = this.context.submitBatchFn(batchToSend);
@@ -1873,36 +1934,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1873
1934
  }
1874
1935
 
1875
1936
  public orderSequentially(callback: () => void): void {
1876
- // If flush mode is already TurnBased we are either
1877
- // nested in another orderSequentially, or
1878
- // the app is flushing manually, in which
1879
- // case this invocation doesn't own
1880
- // flushing.
1881
- if (this.flushMode === FlushMode.TurnBased) {
1882
- this.trackOrderSequentiallyCalls(callback);
1883
- return;
1884
- }
1885
-
1886
- const savedFlushMode = this.flushMode;
1887
- this.setFlushMode(FlushMode.TurnBased);
1888
-
1889
- try {
1890
- this.trackOrderSequentiallyCalls(callback);
1891
- this.flush();
1892
- } finally {
1893
- this.setFlushMode(savedFlushMode);
1894
- }
1895
- }
1896
-
1897
- private trackOrderSequentiallyCalls(callback: () => void): void {
1898
1937
  let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
1938
+
1899
1939
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1900
1940
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1901
1941
  // 1. It would not help, as we flush attach ops as they become available.
1902
1942
  // 2. There is no way to undo process of data store creation.
1903
1943
  checkpoint = this.pendingBatch.checkpoint();
1904
1944
  }
1905
-
1906
1945
  try {
1907
1946
  this._orderSequentiallyCalls++;
1908
1947
  callback();
@@ -1933,6 +1972,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1933
1972
  } finally {
1934
1973
  this._orderSequentiallyCalls--;
1935
1974
  }
1975
+
1976
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1977
+ this.flush();
1978
+ }
1936
1979
  }
1937
1980
 
1938
1981
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
@@ -1982,6 +2025,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1982
2025
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1983
2026
  }
1984
2027
 
2028
+ /**
2029
+ * Are we in the middle of batching ops together?
2030
+ */
2031
+ private currentlyBatching() {
2032
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
2033
+ }
2034
+
1985
2035
  public getQuorum(): IQuorumClients {
1986
2036
  return this.context.quorum;
1987
2037
  }
@@ -2194,10 +2244,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2194
2244
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
2195
2245
  * After GC has run, called to notify this container's nodes of routes that are used in it.
2196
2246
  * @param usedRoutes - The routes that are used in all nodes in this Container.
2197
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
2198
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
2199
2247
  */
2200
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
2248
+ public updateUsedRoutes(usedRoutes: string[]) {
2201
2249
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
2202
2250
  // summarizing is required and asserted by the the summarizer node. We are the root and are
2203
2251
  // always referenced, so the used routes is only self-route (empty string).
@@ -2210,7 +2258,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2210
2258
  }
2211
2259
  }
2212
2260
 
2213
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
2261
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2214
2262
  }
2215
2263
 
2216
2264
  /**
@@ -2682,8 +2730,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2682
2730
  // issue than sending.
2683
2731
  // Please note that this does not change file format, so it can be disabled in the future if this
2684
2732
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
2685
- if (type === ContainerMessageType.Attach &&
2686
- this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
2733
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
2734
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
2687
2735
  if (!this.pendingAttachBatch.push(message)) {
2688
2736
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
2689
2737
  // when queue is not empty.
@@ -2711,18 +2759,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2711
2759
  limit: this.pendingBatch.limit,
2712
2760
  });
2713
2761
  }
2714
- }
2715
-
2716
- if (this._flushMode !== FlushMode.TurnBased) {
2717
- this.flush();
2718
- } else if (!this.flushTrigger) {
2719
- this.flushTrigger = true;
2720
- // Queue a microtask to detect the end of the turn and force a flush.
2721
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
2722
- Promise.resolve().then(() => {
2723
- this.flushTrigger = false;
2762
+ if (!this.currentlyBatching()) {
2724
2763
  this.flush();
2725
- });
2764
+ } else if (!this.flushMicroTaskExists) {
2765
+ this.flushMicroTaskExists = true;
2766
+ // Queue a microtask to detect the end of the turn and force a flush.
2767
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
2768
+ Promise.resolve().then(() => {
2769
+ this.flushMicroTaskExists = false;
2770
+ this.flush();
2771
+ });
2772
+ }
2726
2773
  }
2727
2774
  } catch (error) {
2728
2775
  this.closeFn(error as GenericError);
@@ -2812,12 +2859,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2812
2859
  }
2813
2860
 
2814
2861
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2815
- public async refreshLatestSummaryAck(
2816
- proposalHandle: string | undefined,
2817
- ackHandle: string,
2818
- summaryRefSeq: number,
2819
- summaryLogger: ITelemetryLogger,
2820
- ) {
2862
+ public async refreshLatestSummaryAck(options: IRefreshSummaryAckOptions) {
2863
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
2821
2864
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
2822
2865
  // The call to fetch the snapshot is very expensive and not always needed.
2823
2866
  // It should only be done by the summarizerNode, if required.
@@ -2933,6 +2976,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2933
2976
  // to close current batch.
2934
2977
  this.flush();
2935
2978
 
2979
+ if (this._orderSequentiallyCalls !== 0) {
2980
+ throw new UsageError("can't get state during orderSequentially");
2981
+ }
2982
+
2936
2983
  const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
2937
2984
  if (previousPendingState) {
2938
2985
  return {