@fluidframework/container-runtime 2.1.0-276326 → 2.1.0-281041

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 (236) hide show
  1. package/README.md +74 -21
  2. package/api-extractor/api-extractor.current.json +5 -0
  3. package/api-extractor/api-extractor.legacy.json +1 -1
  4. package/api-extractor.json +1 -1
  5. package/api-report/container-runtime.legacy.public.api.md +9 -0
  6. package/container-runtime.test-files.tar +0 -0
  7. package/dist/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  8. package/dist/blobManager/blobManager.d.ts.map +1 -0
  9. package/dist/{blobManager.js → blobManager/blobManager.js} +42 -83
  10. package/dist/blobManager/blobManager.js.map +1 -0
  11. package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
  12. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  13. package/dist/blobManager/blobManagerSnapSum.js +82 -0
  14. package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
  15. package/dist/blobManager/index.d.ts +7 -0
  16. package/dist/blobManager/index.d.ts.map +1 -0
  17. package/dist/blobManager/index.js +16 -0
  18. package/dist/blobManager/index.js.map +1 -0
  19. package/dist/channelCollection.d.ts +1 -1
  20. package/dist/channelCollection.d.ts.map +1 -1
  21. package/dist/channelCollection.js +40 -8
  22. package/dist/channelCollection.js.map +1 -1
  23. package/dist/containerRuntime.d.ts +15 -10
  24. package/dist/containerRuntime.d.ts.map +1 -1
  25. package/dist/containerRuntime.js +199 -162
  26. package/dist/containerRuntime.js.map +1 -1
  27. package/dist/dataStoreContext.d.ts +5 -0
  28. package/dist/dataStoreContext.d.ts.map +1 -1
  29. package/dist/dataStoreContext.js +16 -5
  30. package/dist/dataStoreContext.js.map +1 -1
  31. package/dist/gc/garbageCollection.d.ts +1 -1
  32. package/dist/gc/garbageCollection.d.ts.map +1 -1
  33. package/dist/gc/garbageCollection.js +16 -10
  34. package/dist/gc/garbageCollection.js.map +1 -1
  35. package/dist/gc/gcDefinitions.d.ts +4 -2
  36. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  37. package/dist/gc/gcDefinitions.js.map +1 -1
  38. package/dist/gc/gcHelpers.d.ts.map +1 -1
  39. package/dist/gc/gcHelpers.js +12 -0
  40. package/dist/gc/gcHelpers.js.map +1 -1
  41. package/dist/gc/gcTelemetry.d.ts +3 -2
  42. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  43. package/dist/gc/gcTelemetry.js +6 -6
  44. package/dist/gc/gcTelemetry.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/legacy.d.ts +1 -1
  49. package/dist/metadata.d.ts +7 -1
  50. package/dist/metadata.d.ts.map +1 -1
  51. package/dist/metadata.js +6 -0
  52. package/dist/metadata.js.map +1 -1
  53. package/dist/opLifecycle/batchManager.d.ts +8 -1
  54. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  55. package/dist/opLifecycle/batchManager.js +37 -16
  56. package/dist/opLifecycle/batchManager.js.map +1 -1
  57. package/dist/opLifecycle/definitions.d.ts +1 -1
  58. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  59. package/dist/opLifecycle/definitions.js.map +1 -1
  60. package/dist/opLifecycle/index.d.ts +1 -1
  61. package/dist/opLifecycle/index.d.ts.map +1 -1
  62. package/dist/opLifecycle/index.js +2 -1
  63. package/dist/opLifecycle/index.js.map +1 -1
  64. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  65. package/dist/opLifecycle/opCompressor.js +12 -8
  66. package/dist/opLifecycle/opCompressor.js.map +1 -1
  67. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  68. package/dist/opLifecycle/opGroupingManager.js +14 -11
  69. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  70. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  71. package/dist/opLifecycle/opSplitter.js +11 -6
  72. package/dist/opLifecycle/opSplitter.js.map +1 -1
  73. package/dist/opLifecycle/outbox.d.ts +22 -6
  74. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  75. package/dist/opLifecycle/outbox.js +43 -21
  76. package/dist/opLifecycle/outbox.js.map +1 -1
  77. package/dist/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  78. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  79. package/dist/opLifecycle/remoteMessageProcessor.js +59 -9
  80. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  81. package/dist/packageVersion.d.ts +1 -1
  82. package/dist/packageVersion.js +1 -1
  83. package/dist/packageVersion.js.map +1 -1
  84. package/dist/pendingStateManager.d.ts +39 -13
  85. package/dist/pendingStateManager.d.ts.map +1 -1
  86. package/dist/pendingStateManager.js +98 -33
  87. package/dist/pendingStateManager.js.map +1 -1
  88. package/dist/public.d.ts +1 -1
  89. package/dist/scheduleManager.js +4 -0
  90. package/dist/scheduleManager.js.map +1 -1
  91. package/dist/summary/index.d.ts +1 -1
  92. package/dist/summary/index.d.ts.map +1 -1
  93. package/dist/summary/index.js +1 -2
  94. package/dist/summary/index.js.map +1 -1
  95. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  96. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  97. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  98. package/dist/summary/summaryFormat.d.ts +0 -1
  99. package/dist/summary/summaryFormat.d.ts.map +1 -1
  100. package/dist/summary/summaryFormat.js +7 -4
  101. package/dist/summary/summaryFormat.js.map +1 -1
  102. package/internal.d.ts +1 -1
  103. package/legacy.d.ts +1 -1
  104. package/lib/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  105. package/lib/blobManager/blobManager.d.ts.map +1 -0
  106. package/lib/{blobManager.js → blobManager/blobManager.js} +40 -83
  107. package/lib/blobManager/blobManager.js.map +1 -0
  108. package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
  109. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  110. package/lib/blobManager/blobManagerSnapSum.js +75 -0
  111. package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
  112. package/lib/blobManager/index.d.ts +7 -0
  113. package/lib/blobManager/index.d.ts.map +1 -0
  114. package/lib/blobManager/index.js +7 -0
  115. package/lib/blobManager/index.js.map +1 -0
  116. package/lib/channelCollection.d.ts +1 -1
  117. package/lib/channelCollection.d.ts.map +1 -1
  118. package/lib/channelCollection.js +40 -8
  119. package/lib/channelCollection.js.map +1 -1
  120. package/lib/containerRuntime.d.ts +15 -10
  121. package/lib/containerRuntime.d.ts.map +1 -1
  122. package/lib/containerRuntime.js +149 -112
  123. package/lib/containerRuntime.js.map +1 -1
  124. package/lib/dataStoreContext.d.ts +5 -0
  125. package/lib/dataStoreContext.d.ts.map +1 -1
  126. package/lib/dataStoreContext.js +17 -6
  127. package/lib/dataStoreContext.js.map +1 -1
  128. package/lib/gc/garbageCollection.d.ts +1 -1
  129. package/lib/gc/garbageCollection.d.ts.map +1 -1
  130. package/lib/gc/garbageCollection.js +16 -10
  131. package/lib/gc/garbageCollection.js.map +1 -1
  132. package/lib/gc/gcDefinitions.d.ts +4 -2
  133. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  134. package/lib/gc/gcDefinitions.js.map +1 -1
  135. package/lib/gc/gcHelpers.d.ts.map +1 -1
  136. package/lib/gc/gcHelpers.js +12 -0
  137. package/lib/gc/gcHelpers.js.map +1 -1
  138. package/lib/gc/gcTelemetry.d.ts +3 -2
  139. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  140. package/lib/gc/gcTelemetry.js +6 -6
  141. package/lib/gc/gcTelemetry.js.map +1 -1
  142. package/lib/index.d.ts +1 -1
  143. package/lib/index.d.ts.map +1 -1
  144. package/lib/index.js.map +1 -1
  145. package/lib/legacy.d.ts +1 -1
  146. package/lib/metadata.d.ts +7 -1
  147. package/lib/metadata.d.ts.map +1 -1
  148. package/lib/metadata.js +4 -1
  149. package/lib/metadata.js.map +1 -1
  150. package/lib/opLifecycle/batchManager.d.ts +8 -1
  151. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  152. package/lib/opLifecycle/batchManager.js +35 -15
  153. package/lib/opLifecycle/batchManager.js.map +1 -1
  154. package/lib/opLifecycle/definitions.d.ts +1 -1
  155. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  156. package/lib/opLifecycle/definitions.js.map +1 -1
  157. package/lib/opLifecycle/index.d.ts +1 -1
  158. package/lib/opLifecycle/index.d.ts.map +1 -1
  159. package/lib/opLifecycle/index.js +1 -1
  160. package/lib/opLifecycle/index.js.map +1 -1
  161. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  162. package/lib/opLifecycle/opCompressor.js +12 -8
  163. package/lib/opLifecycle/opCompressor.js.map +1 -1
  164. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  165. package/lib/opLifecycle/opGroupingManager.js +14 -11
  166. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  167. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  168. package/lib/opLifecycle/opSplitter.js +11 -6
  169. package/lib/opLifecycle/opSplitter.js.map +1 -1
  170. package/lib/opLifecycle/outbox.d.ts +22 -6
  171. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  172. package/lib/opLifecycle/outbox.js +44 -22
  173. package/lib/opLifecycle/outbox.js.map +1 -1
  174. package/lib/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  175. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  176. package/lib/opLifecycle/remoteMessageProcessor.js +57 -7
  177. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  178. package/lib/packageVersion.d.ts +1 -1
  179. package/lib/packageVersion.js +1 -1
  180. package/lib/packageVersion.js.map +1 -1
  181. package/lib/pendingStateManager.d.ts +39 -13
  182. package/lib/pendingStateManager.d.ts.map +1 -1
  183. package/lib/pendingStateManager.js +99 -34
  184. package/lib/pendingStateManager.js.map +1 -1
  185. package/lib/public.d.ts +1 -1
  186. package/lib/scheduleManager.js +4 -0
  187. package/lib/scheduleManager.js.map +1 -1
  188. package/lib/summary/index.d.ts +1 -1
  189. package/lib/summary/index.d.ts.map +1 -1
  190. package/lib/summary/index.js +1 -1
  191. package/lib/summary/index.js.map +1 -1
  192. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  193. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  194. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  195. package/lib/summary/summaryFormat.d.ts +0 -1
  196. package/lib/summary/summaryFormat.d.ts.map +1 -1
  197. package/lib/summary/summaryFormat.js +5 -2
  198. package/lib/summary/summaryFormat.js.map +1 -1
  199. package/package.json +49 -34
  200. package/src/{blobManager.ts → blobManager/blobManager.ts} +57 -123
  201. package/src/blobManager/blobManagerSnapSum.ts +133 -0
  202. package/src/blobManager/index.ts +19 -0
  203. package/src/channelCollection.ts +48 -11
  204. package/src/containerRuntime.ts +213 -158
  205. package/src/dataStoreContext.ts +30 -6
  206. package/src/gc/garbageCollection.ts +17 -12
  207. package/src/gc/gcDefinitions.ts +7 -2
  208. package/src/gc/gcHelpers.ts +18 -6
  209. package/src/gc/gcTelemetry.ts +20 -8
  210. package/src/index.ts +1 -1
  211. package/src/metadata.ts +11 -1
  212. package/src/opLifecycle/README.md +0 -8
  213. package/src/opLifecycle/batchManager.ts +46 -16
  214. package/src/opLifecycle/definitions.ts +1 -1
  215. package/src/opLifecycle/index.ts +8 -1
  216. package/src/opLifecycle/opCompressor.ts +12 -8
  217. package/src/opLifecycle/opGroupingManager.ts +14 -11
  218. package/src/opLifecycle/opSplitter.ts +10 -6
  219. package/src/opLifecycle/outbox.ts +64 -26
  220. package/src/opLifecycle/remoteMessageProcessor.ts +84 -11
  221. package/src/packageVersion.ts +1 -1
  222. package/src/pendingStateManager.ts +177 -60
  223. package/src/scheduleManager.ts +6 -2
  224. package/src/summary/README.md +81 -0
  225. package/src/summary/index.ts +0 -1
  226. package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
  227. package/src/summary/summaryFormat.ts +4 -2
  228. package/src/summary/summaryFormats.md +69 -8
  229. package/tsconfig.json +0 -1
  230. package/dist/blobManager.d.ts.map +0 -1
  231. package/dist/blobManager.js.map +0 -1
  232. package/lib/blobManager.d.ts.map +0 -1
  233. package/lib/blobManager.js.map +0 -1
  234. package/src/summary/images/appTree.png +0 -0
  235. package/src/summary/images/protocolAndAppTree.png +0 -0
  236. package/src/summary/images/summaryTree.png +0 -0
@@ -61,6 +61,7 @@ import {
61
61
  MessageType,
62
62
  ISequencedDocumentMessage,
63
63
  ISignalMessage,
64
+ type ISummaryContext,
64
65
  } from "@fluidframework/driver-definitions/internal";
65
66
  import { readAndParse } from "@fluidframework/driver-utils/internal";
66
67
  import type { IIdCompressor } from "@fluidframework/id-compressor";
@@ -91,7 +92,6 @@ import {
91
92
  } from "@fluidframework/runtime-definitions/internal";
92
93
  import {
93
94
  GCDataBuilder,
94
- ReadAndParseBlob,
95
95
  RequestParser,
96
96
  TelemetryContext,
97
97
  addBlobToSummary,
@@ -126,7 +126,15 @@ import {
126
126
  import { v4 as uuid } from "uuid";
127
127
 
128
128
  import { BindBatchTracker } from "./batchTracker.js";
129
- import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager.js";
129
+ import {
130
+ BlobManager,
131
+ IPendingBlobs,
132
+ blobManagerBasePath,
133
+ blobsTreeName,
134
+ isBlobPath,
135
+ loadBlobManagerLoadInfo,
136
+ type IBlobManagerLoadInfo,
137
+ } from "./blobManager/index.js";
130
138
  import {
131
139
  ChannelCollection,
132
140
  getSummaryForDatastores,
@@ -161,6 +169,7 @@ import {
161
169
  } from "./messageTypes.js";
162
170
  import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js";
163
171
  import {
172
+ BatchId,
164
173
  BatchMessage,
165
174
  IBatch,
166
175
  IBatchCheckpoint,
@@ -173,7 +182,7 @@ import {
173
182
  } from "./opLifecycle/index.js";
174
183
  import { pkgVersion } from "./packageVersion.js";
175
184
  import {
176
- IPendingBatchMessage,
185
+ PendingMessageResubmitData,
177
186
  IPendingLocalState,
178
187
  PendingStateManager,
179
188
  } from "./pendingStateManager.js";
@@ -211,7 +220,6 @@ import {
211
220
  SummaryCollection,
212
221
  SummaryManager,
213
222
  aliasBlobName,
214
- blobsTreeName,
215
223
  chunksBlobName,
216
224
  createRootSummarizerNodeWithGC,
217
225
  electedSummarizerBlobName,
@@ -661,7 +669,7 @@ export const makeLegacySendBatchFn =
661
669
  (batch: IBatch) => {
662
670
  // Default to negative one to match Container.submitBatch behavior
663
671
  let clientSequenceNumber: number = -1;
664
- for (const message of batch.content) {
672
+ for (const message of batch.messages) {
665
673
  clientSequenceNumber = submitFn(
666
674
  MessageType.Operation,
667
675
  // For back-compat (submitFn only works on deserialized content)
@@ -677,23 +685,26 @@ export const makeLegacySendBatchFn =
677
685
  };
678
686
 
679
687
  /** Helper type for type constraints passed through several functions.
688
+ * local - Did this client send the op?
689
+ * savedOp - Is this op being replayed after being serialized (having been sequenced previously)
690
+ * batchStartCsn - The clientSequenceNumber given on submit to the start of this batch
680
691
  * message - The unpacked message. Likely a TypedContainerRuntimeMessage, but could also be a system op
681
692
  * modernRuntimeMessage - Does this appear like a current TypedContainerRuntimeMessage?
682
- * local - Did this client send the op?
683
693
  */
684
- type MessageWithContext =
694
+ type MessageWithContext = {
695
+ local: boolean;
696
+ savedOp?: boolean;
697
+ localOpMetadata?: unknown;
698
+ } & (
685
699
  | {
686
700
  message: InboundSequencedContainerRuntimeMessage;
687
701
  modernRuntimeMessage: true;
688
- local: boolean;
689
- savedOp?: boolean;
690
702
  }
691
703
  | {
692
704
  message: InboundSequencedContainerRuntimeMessageOrSystemMessage;
693
705
  modernRuntimeMessage: false;
694
- local: boolean;
695
- savedOp?: boolean;
696
- };
706
+ }
707
+ );
697
708
 
698
709
  const summarizerRequestUrl = "_summarizer";
699
710
 
@@ -857,18 +868,7 @@ export class ContainerRuntime
857
868
  ]);
858
869
 
859
870
  // read snapshot blobs needed for BlobManager to load
860
- const blobManagerSnapshot = await BlobManager.load(
861
- context.baseSnapshot?.trees[blobsTreeName],
862
- async (id) => {
863
- // IContainerContext storage api return type still has undefined in 0.39 package version.
864
- // So once we release 0.40 container-defn package we can remove this check.
865
- assert(
866
- context.storage !== undefined,
867
- 0x256 /* "storage undefined in attached container" */,
868
- );
869
- return readAndParse(context.storage, id);
870
- },
871
- );
871
+ const blobManagerSnapshot = await loadBlobManagerLoadInfo(context);
872
872
 
873
873
  const messageAtLastSummary = lastMessageFromMetadata(metadata);
874
874
 
@@ -989,11 +989,7 @@ export class ContainerRuntime
989
989
  }
990
990
  };
991
991
 
992
- const disableCompression = mc.config.getBoolean(
993
- "Fluid.ContainerRuntime.CompressionDisabled",
994
- );
995
992
  const compressionLz4 =
996
- disableCompression !== true &&
997
993
  compressionOptions.minimumBatchSizeInBytes !== Infinity &&
998
994
  compressionOptions.compressionAlgorithm === "lz4";
999
995
 
@@ -1013,9 +1009,7 @@ export class ContainerRuntime
1013
1009
  },
1014
1010
  );
1015
1011
 
1016
- const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {
1017
- disableCompression,
1018
- };
1012
+ const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {};
1019
1013
 
1020
1014
  const runtime = new containerRuntimeCtor(
1021
1015
  context,
@@ -1361,6 +1355,13 @@ export class ContainerRuntime
1361
1355
  */
1362
1356
  private readonly loadedFromVersionId: string | undefined;
1363
1357
 
1358
+ private readonly isSnapshotInstanceOfISnapshot: boolean | undefined;
1359
+
1360
+ /**
1361
+ * The summary context of the last acked summary. The properties from this as used when uploading a summary.
1362
+ */
1363
+ private lastAckedSummaryContext: ISummaryContext | undefined;
1364
+
1364
1365
  /**
1365
1366
  * It a cache for holding mapping for loading groupIds with its snapshot from the service. Add expiry policy of 1 minute.
1366
1367
  * Starting with 1 min and based on recorded usage we can tweak it later on.
@@ -1513,9 +1514,6 @@ export class ContainerRuntime
1513
1514
  this.disableAttachReorder = this.mc.config.getBoolean(
1514
1515
  "Fluid.ContainerRuntime.disableAttachOpReorder",
1515
1516
  );
1516
- const disableChunking = this.mc.config.getBoolean(
1517
- "Fluid.ContainerRuntime.CompressionChunkingDisabled",
1518
- );
1519
1517
 
1520
1518
  const opGroupingManager = new OpGroupingManager(
1521
1519
  {
@@ -1532,7 +1530,7 @@ export class ContainerRuntime
1532
1530
  const opSplitter = new OpSplitter(
1533
1531
  chunks,
1534
1532
  this.submitBatchFn,
1535
- disableChunking === true ? Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1533
+ runtimeOptions.chunkSizeInBytes,
1536
1534
  runtimeOptions.maxBatchSizeInBytes,
1537
1535
  this.mc.logger,
1538
1536
  );
@@ -1550,10 +1548,6 @@ export class ContainerRuntime
1550
1548
  clientId: () => this.clientId,
1551
1549
  close: this.closeFn,
1552
1550
  connected: () => this.connected,
1553
- reSubmit: (message: IPendingBatchMessage) => {
1554
- this.reSubmit(message);
1555
- this.flush();
1556
- },
1557
1551
  reSubmitBatch: this.reSubmitBatch.bind(this),
1558
1552
  isActiveConnection: () => this.innerDeltaManager.active,
1559
1553
  isAttached: () => this.attachState !== AttachState.Detached,
@@ -1666,6 +1660,10 @@ export class ContainerRuntime
1666
1660
 
1667
1661
  const parentContext = wrapContext(this);
1668
1662
 
1663
+ if (snapshotWithContents !== undefined) {
1664
+ this.isSnapshotInstanceOfISnapshot = true;
1665
+ }
1666
+
1669
1667
  // Due to a mismatch between different layers in terms of
1670
1668
  // what is the interface of passing signals, we need the
1671
1669
  // downstream stores to wrap the signal.
@@ -1922,7 +1920,6 @@ export class ContainerRuntime
1922
1920
  sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
1923
1921
  featureGates: JSON.stringify({
1924
1922
  ...featureGatesForTelemetry,
1925
- disableChunking,
1926
1923
  disableAttachReorder: this.disableAttachReorder,
1927
1924
  disablePartialFlush,
1928
1925
  closeSummarizerDelayOverride,
@@ -2168,9 +2165,13 @@ export class ContainerRuntime
2168
2165
  let childTree = snapshotTree;
2169
2166
  for (const part of pathParts) {
2170
2167
  if (hasIsolatedChannels) {
2171
- childTree = childTree?.trees[channelsTreeName];
2168
+ // TODO Why are we non null asserting here
2169
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2170
+ childTree = childTree.trees[channelsTreeName]!;
2172
2171
  }
2173
- childTree = childTree?.trees[part];
2172
+ // TODO Why are we non null asserting here
2173
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2174
+ childTree = childTree.trees[part]!;
2174
2175
  }
2175
2176
  return childTree;
2176
2177
  }
@@ -2221,8 +2222,10 @@ export class ContainerRuntime
2221
2222
  return this.resolveHandle(requestParser.createSubRequest(1));
2222
2223
  }
2223
2224
 
2224
- if (id === BlobManager.basePath && requestParser.isLeaf(2)) {
2225
- const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
2225
+ if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
2226
+ // TODO why are we non null asserting here?
2227
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2228
+ const blob = await this.blobManager.getBlob(requestParser.pathParts[1]!);
2226
2229
  return blob
2227
2230
  ? {
2228
2231
  status: 200,
@@ -2619,26 +2622,38 @@ export class ContainerRuntime
2619
2622
  // but will not modify the contents object (likely it will replace it on the message).
2620
2623
  const messageCopy = { ...messageArg };
2621
2624
  const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2622
- for (const message of this.remoteMessageProcessor.process(messageCopy)) {
2623
- const msg: MessageWithContext = modernRuntimeMessage
2624
- ? {
2625
- // Cast it since we expect it to be this based on modernRuntimeMessage computation above.
2626
- // There is nothing really ensuring that anytime original message.type is Operation that
2627
- // the result messages will be so. In the end modern bool being true only directs to
2628
- // throw error if ultimately unrecognized without compat details saying otherwise.
2629
- message: message as InboundSequencedContainerRuntimeMessage,
2630
- local,
2631
- modernRuntimeMessage,
2632
- }
2633
- : // Unrecognized message will be ignored.
2634
- {
2635
- message,
2636
- local,
2637
- modernRuntimeMessage,
2638
- };
2639
- msg.savedOp = savedOp;
2640
-
2641
- // ensure that we observe any re-entrancy, and if needed, rebase ops
2625
+ if (modernRuntimeMessage) {
2626
+ const processResult = this.remoteMessageProcessor.process(messageCopy);
2627
+ if (processResult === undefined) {
2628
+ // This means the incoming message is an incomplete part of a message or batch
2629
+ // and we need to process more messages before the rest of the system can understand it.
2630
+ return;
2631
+ }
2632
+ const batchStartCsn = processResult.batchStartCsn;
2633
+ const batch = processResult.messages;
2634
+ const messages: {
2635
+ message: InboundSequencedContainerRuntimeMessage;
2636
+ localOpMetadata: unknown;
2637
+ }[] = local
2638
+ ? this.pendingStateManager.processPendingLocalBatch(batch, batchStartCsn)
2639
+ : batch.map((message) => ({ message, localOpMetadata: undefined }));
2640
+ messages.forEach(({ message, localOpMetadata }) => {
2641
+ const msg: MessageWithContext = {
2642
+ message,
2643
+ local,
2644
+ modernRuntimeMessage,
2645
+ savedOp,
2646
+ localOpMetadata,
2647
+ };
2648
+ this.ensureNoDataModelChanges(() => this.processCore(msg));
2649
+ });
2650
+ } else {
2651
+ const msg: MessageWithContext = {
2652
+ message: messageCopy as InboundSequencedContainerRuntimeMessageOrSystemMessage,
2653
+ local,
2654
+ modernRuntimeMessage,
2655
+ savedOp,
2656
+ };
2642
2657
  this.ensureNoDataModelChanges(() => this.processCore(msg));
2643
2658
  }
2644
2659
  }
@@ -2649,7 +2664,7 @@ export class ContainerRuntime
2649
2664
  * Direct the message to the correct subsystem for processing, and implement other side effects
2650
2665
  */
2651
2666
  private processCore(messageWithContext: MessageWithContext) {
2652
- const { message, local } = messageWithContext;
2667
+ const { message, local, localOpMetadata } = messageWithContext;
2653
2668
 
2654
2669
  // Intercept to reduce minimum sequence number to the delta manager's minimum sequence number.
2655
2670
  // Sequence numbers are not guaranteed to follow any sort of order. Re-entrancy is one of those situations
@@ -2669,22 +2684,12 @@ export class ContainerRuntime
2669
2684
  this._processedClientSequenceNumber = message.clientSequenceNumber;
2670
2685
 
2671
2686
  try {
2672
- // See commit that added this assert for more details.
2673
- // These calls should be made for all but chunked ops:
2674
- // 1) this.pendingStateManager.processPendingLocalMessage() below
2675
- // 2) this.resetReconnectCount() below
2687
+ // RemoteMessageProcessor would have already reconstituted Chunked Ops into the original op type
2676
2688
  assert(
2677
2689
  message.type !== ContainerMessageType.ChunkedOp,
2678
2690
  0x93b /* we should never get here with chunked ops */,
2679
2691
  );
2680
2692
 
2681
- let localOpMetadata: unknown;
2682
- if (local && messageWithContext.modernRuntimeMessage) {
2683
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(
2684
- messageWithContext.message,
2685
- );
2686
- }
2687
-
2688
2693
  // If there are no more pending messages after processing a local message,
2689
2694
  // the document is no longer dirty.
2690
2695
  if (!this.hasPendingMessages()) {
@@ -2880,14 +2885,16 @@ export class ContainerRuntime
2880
2885
  /**
2881
2886
  * Flush the pending ops manually.
2882
2887
  * This method is expected to be called at the end of a batch.
2888
+ * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
2889
+ * with the given Batch ID, which must be preserved
2883
2890
  */
2884
- private flush(): void {
2891
+ private flush(resubmittingBatchId?: BatchId): void {
2885
2892
  assert(
2886
2893
  this._orderSequentiallyCalls === 0,
2887
2894
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */,
2888
2895
  );
2889
2896
 
2890
- this.outbox.flush();
2897
+ this.outbox.flush(resubmittingBatchId);
2891
2898
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
2892
2899
  }
2893
2900
 
@@ -2901,7 +2908,7 @@ export class ContainerRuntime
2901
2908
  // Note: we are not touching any batches other than mainBatch here, for two reasons:
2902
2909
  // 1. It would not help, as other batches are flushed independently from main batch.
2903
2910
  // 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
2904
- checkpoint = this.outbox.checkpoint().mainBatch;
2911
+ checkpoint = this.outbox.getBatchCheckpoints().mainBatch;
2905
2912
  }
2906
2913
  try {
2907
2914
  this._orderSequentiallyCalls++;
@@ -3044,7 +3051,7 @@ export class ContainerRuntime
3044
3051
  }
3045
3052
 
3046
3053
  /**
3047
- * Are we in the middle of batching ops together?
3054
+ * Typically ops are batched and later flushed together, but in some cases we want to flush immediately.
3048
3055
  */
3049
3056
  private currentlyBatching() {
3050
3057
  return this.flushMode !== FlushMode.Immediate || this._orderSequentiallyCalls !== 0;
@@ -3360,7 +3367,7 @@ export class ContainerRuntime
3360
3367
  * blob manager.
3361
3368
  */
3362
3369
  public getNodeType(nodePath: string): GCNodeType {
3363
- if (this.isBlobPath(nodePath)) {
3370
+ if (isBlobPath(nodePath)) {
3364
3371
  return GCNodeType.Blob;
3365
3372
  }
3366
3373
  return this.channelCollection.getGCNodeType(nodePath) ?? GCNodeType.Other;
@@ -3379,7 +3386,7 @@ export class ContainerRuntime
3379
3386
 
3380
3387
  switch (this.getNodeType(nodePath)) {
3381
3388
  case GCNodeType.Blob:
3382
- return [BlobManager.basePath];
3389
+ return [blobManagerBasePath];
3383
3390
  case GCNodeType.DataStore:
3384
3391
  case GCNodeType.SubDataStore:
3385
3392
  return this.channelCollection.getDataStorePackagePath(nodePath);
@@ -3388,17 +3395,6 @@ export class ContainerRuntime
3388
3395
  }
3389
3396
  }
3390
3397
 
3391
- /**
3392
- * Returns whether a given path is for attachment blobs that are in the format - "/BlobManager.basePath/...".
3393
- */
3394
- private isBlobPath(path: string): boolean {
3395
- const pathParts = path.split("/");
3396
- if (pathParts.length < 2 || pathParts[1] !== BlobManager.basePath) {
3397
- return false;
3398
- }
3399
- return true;
3400
- }
3401
-
3402
3398
  /**
3403
3399
  * From a given list of routes, separate and return routes that belong to blob manager and data stores.
3404
3400
  * @param routes - A list of routes that can belong to data stores or blob manager.
@@ -3409,7 +3405,7 @@ export class ContainerRuntime
3409
3405
  const blobManagerRoutes: string[] = [];
3410
3406
  const dataStoreRoutes: string[] = [];
3411
3407
  for (const route of routes) {
3412
- if (this.isBlobPath(route)) {
3408
+ if (isBlobPath(route)) {
3413
3409
  blobManagerRoutes.push(route);
3414
3410
  } else {
3415
3411
  dataStoreRoutes.push(route);
@@ -3555,7 +3551,7 @@ export class ContainerRuntime
3555
3551
  summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
3556
3552
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
3557
3553
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
3558
- const lastAck = this.summaryCollection.latestAck;
3554
+ const lastAckedContext = this.lastAckedSummaryContext;
3559
3555
 
3560
3556
  const startSummaryResult = this.summarizerNode.startSummary(
3561
3557
  summaryRefSeqNum,
@@ -3622,10 +3618,10 @@ export class ContainerRuntime
3622
3618
  0x395 /* it's one and the same thing */,
3623
3619
  );
3624
3620
 
3625
- if (lastAck !== this.summaryCollection.latestAck) {
3621
+ if (lastAckedContext !== this.lastAckedSummaryContext) {
3626
3622
  return {
3627
3623
  continue: false,
3628
- error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
3624
+ error: `Last summary changed while summarizing. ${this.lastAckedSummaryContext} !== ${lastAckedContext}`,
3629
3625
  };
3630
3626
  }
3631
3627
  return { continue: true };
@@ -3697,7 +3693,9 @@ export class ContainerRuntime
3697
3693
  // Counting dataStores and handles
3698
3694
  // Because handles are unchanged dataStores in the current logic,
3699
3695
  // summarized dataStore count is total dataStore count minus handle count
3700
- const dataStoreTree = summaryTree.tree[channelsTreeName];
3696
+ // TODO why are we non null asserting here
3697
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3698
+ const dataStoreTree = summaryTree.tree[channelsTreeName]!;
3701
3699
 
3702
3700
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
3703
3701
  const handleCount = Object.values(dataStoreTree.tree).filter(
@@ -3733,18 +3731,11 @@ export class ContainerRuntime
3733
3731
  };
3734
3732
  }
3735
3733
 
3736
- const summaryContext =
3737
- lastAck === undefined
3738
- ? {
3739
- proposalHandle: undefined,
3740
- ackHandle: this.loadedFromVersionId,
3741
- referenceSequenceNumber: summaryRefSeqNum,
3742
- }
3743
- : {
3744
- proposalHandle: lastAck.summaryOp.contents.handle,
3745
- ackHandle: lastAck.summaryAck.contents.handle,
3746
- referenceSequenceNumber: summaryRefSeqNum,
3747
- };
3734
+ const summaryContext: ISummaryContext = {
3735
+ proposalHandle: this.lastAckedSummaryContext?.proposalHandle ?? undefined,
3736
+ ackHandle: this.lastAckedSummaryContext?.ackHandle ?? this.loadedFromVersionId,
3737
+ referenceSequenceNumber: summaryRefSeqNum,
3738
+ };
3748
3739
 
3749
3740
  let handle: string;
3750
3741
  try {
@@ -4035,7 +4026,9 @@ export class ContainerRuntime
4035
4026
  this.outbox.submit(message);
4036
4027
  }
4037
4028
 
4038
- if (!this.currentlyBatching()) {
4029
+ // Note: Technically, the system "always" batches - if this case is true we'll just have a single-message batch.
4030
+ const flushImmediatelyOnSubmit = !this.currentlyBatching();
4031
+ if (flushImmediatelyOnSubmit) {
4039
4032
  this.flush();
4040
4033
  } else {
4041
4034
  this.scheduleFlush();
@@ -4116,16 +4109,22 @@ export class ContainerRuntime
4116
4109
  }
4117
4110
  }
4118
4111
 
4119
- private reSubmitBatch(batch: IPendingBatchMessage[]) {
4112
+ private reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId) {
4120
4113
  this.orderSequentially(() => {
4121
4114
  for (const message of batch) {
4122
4115
  this.reSubmit(message);
4123
4116
  }
4124
4117
  });
4125
- this.flush();
4118
+
4119
+ // Only include Batch ID if "Offline Load" feature is enabled
4120
+ // It's only needed to identify batches across container forks arising from misuse of offline load.
4121
+ const includeBatchId =
4122
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
4123
+
4124
+ this.flush(includeBatchId ? batchId : undefined);
4126
4125
  }
4127
4126
 
4128
- private reSubmit(message: IPendingBatchMessage) {
4127
+ private reSubmit(message: PendingMessageResubmitData) {
4129
4128
  // Need to parse from string for back-compat
4130
4129
  const containerRuntimeMessage = this.parseLocalOpContent(message.content);
4131
4130
  this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
@@ -4228,84 +4227,140 @@ export class ContainerRuntime
4228
4227
  const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
4229
4228
  // proposalHandle is always passed from RunningSummarizer.
4230
4229
  assert(proposalHandle !== undefined, 0x766 /* proposalHandle should be available */);
4231
- const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
4232
4230
  const result = await this.summarizerNode.refreshLatestSummary(
4233
4231
  proposalHandle,
4234
4232
  summaryRefSeq,
4235
4233
  );
4236
4234
 
4235
+ /* eslint-disable jsdoc/check-indentation */
4237
4236
  /**
4238
- * When refreshing a summary ack, this check indicates a new ack of a summary that is newer than the
4239
- * current summary that is tracked, but this summarizer runtime did not produce/track that summary. Thus
4240
- * it needs to refresh its state. Today refresh is done by fetching the latest snapshot to update the cache
4241
- * and then close as the current main client is likely to be re-elected as the parent summarizer again.
4237
+ * If the snapshot corresponding to the ack is not tracked by this client, it was submitted by another client.
4238
+ * Take action as per the following scenarios:
4239
+ * 1. If that snapshot is older than the one tracked by this client, ignore the ack because only the latest
4240
+ * snapshot is tracked.
4241
+ * 2. If that snapshot is newer, attempt to fetch the latest snapshot and do one of the following:
4242
+ * 2.1. If the fetched snapshot is same or newer than the one for which ack was received, close this client.
4243
+ * The next summarizer client will likely start from this snapshot and get out of this state. Fetching
4244
+ * the snapshot updates the cache for this client so if it's re-elected as summarizer, this will prevent
4245
+ * any thrashing.
4246
+ * 2.2. If the fetched snapshot is older than the one for which ack was received, ignore the ack. This can
4247
+ * happen in scenarios where the snapshot for the ack was lost in storage (in scenarios like DB rollback,
4248
+ * etc.) but the summary ack is still there because it's tracked a different service. In such cases,
4249
+ * ignoring the ack is the correct thing to do because the latest snapshot in storage is not the one for
4250
+ * the ack but is still the one tracked by this client. If we were to close the summarizer like in the
4251
+ * previous scenario, it will result in this document stuck in this state in a loop.
4242
4252
  */
4243
- if (!result.isSummaryTracked && result.isSummaryNewer) {
4244
- await this.fetchLatestSnapshotAndClose(
4245
- summaryLogger,
4246
- {
4247
- eventName: "RefreshLatestSummaryAckFetch",
4248
- ackHandle,
4249
- targetSequenceNumber: summaryRefSeq,
4250
- },
4251
- readAndParseBlob,
4252
- );
4253
+ /* eslint-enable jsdoc/check-indentation */
4254
+ if (!result.isSummaryTracked) {
4255
+ if (result.isSummaryNewer) {
4256
+ await this.fetchLatestSnapshotAndMaybeClose(summaryRefSeq, ackHandle, summaryLogger);
4257
+ }
4253
4258
  return;
4254
4259
  }
4255
4260
 
4256
4261
  // Notify the garbage collector so it can update its latest summary state.
4257
4262
  await this.garbageCollector.refreshLatestSummary(result);
4263
+
4264
+ // If we here, the ack was tracked by this client. Update the summary context of the last ack.
4265
+ this.lastAckedSummaryContext = {
4266
+ proposalHandle,
4267
+ ackHandle,
4268
+ referenceSequenceNumber: summaryRefSeq,
4269
+ };
4258
4270
  }
4259
4271
 
4260
4272
  /**
4261
- * Fetches the latest snapshot from storage and closes the container. This is done in cases where
4262
- * the last known snapshot is older than the latest one. This will ensure that the latest snapshot
4263
- * is downloaded and we don't end up loading snapshot from cache.
4273
+ * Fetches the latest snapshot from storage. If the fetched snapshot is same or newer than the one for which ack
4274
+ * was received, close this client. Fetching the snapshot will update the cache for this client so if it's
4275
+ * re-elected as summarizer, this will prevent any thrashing.
4276
+ * If the fetched snapshot is older than the one for which ack was received, ignore the ack and return. This can
4277
+ * happen in scenarios where the snapshot for the ack was lost in storage in scenarios like DB rollback, etc.
4264
4278
  */
4265
- private async fetchLatestSnapshotAndClose(
4279
+ private async fetchLatestSnapshotAndMaybeClose(
4280
+ targetRefSeq: number,
4281
+ targetAckHandle: string,
4266
4282
  logger: ITelemetryLoggerExt,
4267
- event: ITelemetryGenericEventExt,
4268
- readAndParseBlob: ReadAndParseBlob,
4269
4283
  ) {
4270
- await PerformanceEvent.timedExecAsync(
4284
+ const fetchedSnapshotRefSeq = await PerformanceEvent.timedExecAsync(
4271
4285
  logger,
4272
- event,
4286
+ { eventName: "RefreshLatestSummaryAckFetch" },
4273
4287
  async (perfEvent: {
4274
4288
  end: (arg0: {
4275
- getVersionDuration?: number | undefined;
4276
- getSnapshotDuration?: number | undefined;
4277
- snapshotRefSeq?: number | undefined;
4278
- snapshotVersion?: string | undefined;
4289
+ details: {
4290
+ getVersionDuration?: number | undefined;
4291
+ getSnapshotDuration?: number | undefined;
4292
+ snapshotRefSeq?: number | undefined;
4293
+ snapshotVersion?: string | undefined;
4294
+ newerSnapshotPresent?: boolean | undefined;
4295
+ targetRefSeq?: number | undefined;
4296
+ targetAckHandle?: string | undefined;
4297
+ };
4279
4298
  }) => void;
4280
4299
  }) => {
4281
- const stats: {
4300
+ const props: {
4282
4301
  getVersionDuration?: number;
4283
4302
  getSnapshotDuration?: number;
4284
4303
  snapshotRefSeq?: number;
4285
4304
  snapshotVersion?: string;
4286
- } = {};
4305
+ newerSnapshotPresent?: boolean | undefined;
4306
+ targetRefSeq?: number | undefined;
4307
+ targetAckHandle?: string | undefined;
4308
+ } = { targetRefSeq, targetAckHandle };
4287
4309
  const trace = Trace.start();
4288
4310
 
4289
- const versions = await this.storage.getVersions(
4290
- null,
4291
- 1,
4292
- "prefetchLatestSummaryBeforeClose",
4293
- FetchSource.noCache,
4294
- );
4295
- assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
4296
- stats.getVersionDuration = trace.trace().duration;
4311
+ let snapshotTree: ISnapshotTree | null;
4312
+ const scenarioName = "RefreshLatestSummaryAckFetch";
4313
+ // If loader supplied us the ISnapshot when loading, the new getSnapshotApi is supported and feature gate is ON, then use the
4314
+ // new API, otherwise it will reduce the service performance because the service will need to recalculate the full snapshot
4315
+ // in case previously getSnapshotApi was used and now we use the getVersions API.
4316
+ if (
4317
+ this.isSnapshotInstanceOfISnapshot &&
4318
+ this.storage.getSnapshot !== undefined &&
4319
+ this.mc.config.getBoolean("Fluid.Container.UseLoadingGroupIdForSnapshotFetch2") ===
4320
+ true
4321
+ ) {
4322
+ const snapshot = await this.storage.getSnapshot({
4323
+ scenarioName,
4324
+ fetchSource: FetchSource.noCache,
4325
+ });
4326
+ const id = snapshot.snapshotTree.id;
4327
+ assert(id !== undefined, "id of the fetched snapshot should be defined");
4328
+ props.snapshotVersion = id;
4329
+ snapshotTree = snapshot.snapshotTree;
4330
+ } else {
4331
+ const versions = await this.storage.getVersions(
4332
+ null,
4333
+ 1,
4334
+ scenarioName,
4335
+ FetchSource.noCache,
4336
+ );
4337
+ assert(
4338
+ !!versions && !!versions[0],
4339
+ 0x137 /* "Failed to get version from storage" */,
4340
+ );
4341
+ snapshotTree = await this.storage.getSnapshotTree(versions[0]);
4342
+ assert(!!snapshotTree, 0x138 /* "Failed to get snapshot from storage" */);
4343
+ props.snapshotVersion = versions[0].id;
4344
+ }
4297
4345
 
4298
- const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
4299
- assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
4300
- stats.getSnapshotDuration = trace.trace().duration;
4301
- const latestSnapshotRefSeq = await seqFromTree(maybeSnapshot, readAndParseBlob);
4302
- stats.snapshotRefSeq = latestSnapshotRefSeq;
4303
- stats.snapshotVersion = versions[0].id;
4346
+ props.getSnapshotDuration = trace.trace().duration;
4347
+ const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
4348
+ const snapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
4349
+ props.snapshotRefSeq = snapshotRefSeq;
4350
+ props.newerSnapshotPresent = snapshotRefSeq >= targetRefSeq;
4304
4351
 
4305
- perfEvent.end(stats);
4352
+ perfEvent.end({ details: props });
4353
+ return snapshotRefSeq;
4306
4354
  },
4307
4355
  );
4308
4356
 
4357
+ // If the snapshot that was fetched is older than the target snapshot, return. The summarizer will not be closed
4358
+ // because the snapshot is likely deleted from storage and it so, closing the summarizer will result in the
4359
+ // document being stuck in this state.
4360
+ if (fetchedSnapshotRefSeq < targetRefSeq) {
4361
+ return;
4362
+ }
4363
+
4309
4364
  await delay(this.closeSummarizerDelayMs);
4310
4365
  this._summarizer?.stop("latestSummaryStateStale");
4311
4366
  this.disposeFn();