@fluidframework/container-runtime 2.1.0-276985 → 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 (196) hide show
  1. package/README.md +71 -18
  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/blobManager.d.ts +10 -0
  8. package/dist/blobManager/blobManager.d.ts.map +1 -1
  9. package/dist/blobManager/blobManager.js +19 -0
  10. package/dist/blobManager/blobManager.js.map +1 -1
  11. package/dist/channelCollection.d.ts +1 -1
  12. package/dist/channelCollection.d.ts.map +1 -1
  13. package/dist/channelCollection.js +40 -8
  14. package/dist/channelCollection.js.map +1 -1
  15. package/dist/containerRuntime.d.ts +14 -5
  16. package/dist/containerRuntime.d.ts.map +1 -1
  17. package/dist/containerRuntime.js +142 -98
  18. package/dist/containerRuntime.js.map +1 -1
  19. package/dist/dataStoreContext.d.ts +4 -0
  20. package/dist/dataStoreContext.d.ts.map +1 -1
  21. package/dist/dataStoreContext.js +9 -3
  22. package/dist/dataStoreContext.js.map +1 -1
  23. package/dist/gc/garbageCollection.d.ts +1 -1
  24. package/dist/gc/garbageCollection.d.ts.map +1 -1
  25. package/dist/gc/garbageCollection.js +14 -8
  26. package/dist/gc/garbageCollection.js.map +1 -1
  27. package/dist/gc/gcDefinitions.d.ts +4 -2
  28. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  29. package/dist/gc/gcDefinitions.js.map +1 -1
  30. package/dist/gc/gcHelpers.d.ts.map +1 -1
  31. package/dist/gc/gcHelpers.js +12 -0
  32. package/dist/gc/gcHelpers.js.map +1 -1
  33. package/dist/gc/gcTelemetry.d.ts +3 -2
  34. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  35. package/dist/gc/gcTelemetry.js +6 -6
  36. package/dist/gc/gcTelemetry.js.map +1 -1
  37. package/dist/legacy.d.ts +1 -1
  38. package/dist/metadata.d.ts +7 -1
  39. package/dist/metadata.d.ts.map +1 -1
  40. package/dist/metadata.js +6 -0
  41. package/dist/metadata.js.map +1 -1
  42. package/dist/opLifecycle/batchManager.d.ts +8 -1
  43. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  44. package/dist/opLifecycle/batchManager.js +37 -16
  45. package/dist/opLifecycle/batchManager.js.map +1 -1
  46. package/dist/opLifecycle/definitions.d.ts +1 -1
  47. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  48. package/dist/opLifecycle/definitions.js.map +1 -1
  49. package/dist/opLifecycle/index.d.ts +1 -1
  50. package/dist/opLifecycle/index.d.ts.map +1 -1
  51. package/dist/opLifecycle/index.js +2 -1
  52. package/dist/opLifecycle/index.js.map +1 -1
  53. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  54. package/dist/opLifecycle/opCompressor.js +12 -8
  55. package/dist/opLifecycle/opCompressor.js.map +1 -1
  56. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  57. package/dist/opLifecycle/opGroupingManager.js +14 -11
  58. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  60. package/dist/opLifecycle/opSplitter.js +11 -6
  61. package/dist/opLifecycle/opSplitter.js.map +1 -1
  62. package/dist/opLifecycle/outbox.d.ts +22 -6
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  64. package/dist/opLifecycle/outbox.js +43 -21
  65. package/dist/opLifecycle/outbox.js.map +1 -1
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts +6 -6
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  68. package/dist/opLifecycle/remoteMessageProcessor.js +18 -6
  69. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/pendingStateManager.d.ts +37 -13
  74. package/dist/pendingStateManager.d.ts.map +1 -1
  75. package/dist/pendingStateManager.js +95 -45
  76. package/dist/pendingStateManager.js.map +1 -1
  77. package/dist/public.d.ts +1 -1
  78. package/dist/scheduleManager.js +4 -0
  79. package/dist/scheduleManager.js.map +1 -1
  80. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  81. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  82. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  83. package/dist/summary/summaryFormat.d.ts.map +1 -1
  84. package/dist/summary/summaryFormat.js +4 -1
  85. package/dist/summary/summaryFormat.js.map +1 -1
  86. package/internal.d.ts +1 -1
  87. package/legacy.d.ts +1 -1
  88. package/lib/blobManager/blobManager.d.ts +10 -0
  89. package/lib/blobManager/blobManager.d.ts.map +1 -1
  90. package/lib/blobManager/blobManager.js +19 -0
  91. package/lib/blobManager/blobManager.js.map +1 -1
  92. package/lib/channelCollection.d.ts +1 -1
  93. package/lib/channelCollection.d.ts.map +1 -1
  94. package/lib/channelCollection.js +40 -8
  95. package/lib/channelCollection.js.map +1 -1
  96. package/lib/containerRuntime.d.ts +14 -5
  97. package/lib/containerRuntime.d.ts.map +1 -1
  98. package/lib/containerRuntime.js +142 -98
  99. package/lib/containerRuntime.js.map +1 -1
  100. package/lib/dataStoreContext.d.ts +4 -0
  101. package/lib/dataStoreContext.d.ts.map +1 -1
  102. package/lib/dataStoreContext.js +10 -4
  103. package/lib/dataStoreContext.js.map +1 -1
  104. package/lib/gc/garbageCollection.d.ts +1 -1
  105. package/lib/gc/garbageCollection.d.ts.map +1 -1
  106. package/lib/gc/garbageCollection.js +14 -8
  107. package/lib/gc/garbageCollection.js.map +1 -1
  108. package/lib/gc/gcDefinitions.d.ts +4 -2
  109. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  110. package/lib/gc/gcDefinitions.js.map +1 -1
  111. package/lib/gc/gcHelpers.d.ts.map +1 -1
  112. package/lib/gc/gcHelpers.js +12 -0
  113. package/lib/gc/gcHelpers.js.map +1 -1
  114. package/lib/gc/gcTelemetry.d.ts +3 -2
  115. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  116. package/lib/gc/gcTelemetry.js +6 -6
  117. package/lib/gc/gcTelemetry.js.map +1 -1
  118. package/lib/legacy.d.ts +1 -1
  119. package/lib/metadata.d.ts +7 -1
  120. package/lib/metadata.d.ts.map +1 -1
  121. package/lib/metadata.js +4 -1
  122. package/lib/metadata.js.map +1 -1
  123. package/lib/opLifecycle/batchManager.d.ts +8 -1
  124. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  125. package/lib/opLifecycle/batchManager.js +35 -15
  126. package/lib/opLifecycle/batchManager.js.map +1 -1
  127. package/lib/opLifecycle/definitions.d.ts +1 -1
  128. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  129. package/lib/opLifecycle/definitions.js.map +1 -1
  130. package/lib/opLifecycle/index.d.ts +1 -1
  131. package/lib/opLifecycle/index.d.ts.map +1 -1
  132. package/lib/opLifecycle/index.js +1 -1
  133. package/lib/opLifecycle/index.js.map +1 -1
  134. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  135. package/lib/opLifecycle/opCompressor.js +12 -8
  136. package/lib/opLifecycle/opCompressor.js.map +1 -1
  137. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  138. package/lib/opLifecycle/opGroupingManager.js +14 -11
  139. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  140. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  141. package/lib/opLifecycle/opSplitter.js +11 -6
  142. package/lib/opLifecycle/opSplitter.js.map +1 -1
  143. package/lib/opLifecycle/outbox.d.ts +22 -6
  144. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  145. package/lib/opLifecycle/outbox.js +44 -22
  146. package/lib/opLifecycle/outbox.js.map +1 -1
  147. package/lib/opLifecycle/remoteMessageProcessor.d.ts +6 -6
  148. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  149. package/lib/opLifecycle/remoteMessageProcessor.js +18 -6
  150. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  151. package/lib/packageVersion.d.ts +1 -1
  152. package/lib/packageVersion.js +1 -1
  153. package/lib/packageVersion.js.map +1 -1
  154. package/lib/pendingStateManager.d.ts +37 -13
  155. package/lib/pendingStateManager.d.ts.map +1 -1
  156. package/lib/pendingStateManager.js +95 -45
  157. package/lib/pendingStateManager.js.map +1 -1
  158. package/lib/public.d.ts +1 -1
  159. package/lib/scheduleManager.js +4 -0
  160. package/lib/scheduleManager.js.map +1 -1
  161. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  162. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  163. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  164. package/lib/summary/summaryFormat.d.ts.map +1 -1
  165. package/lib/summary/summaryFormat.js +4 -1
  166. package/lib/summary/summaryFormat.js.map +1 -1
  167. package/package.json +45 -30
  168. package/src/blobManager/blobManager.ts +19 -0
  169. package/src/channelCollection.ts +48 -11
  170. package/src/containerRuntime.ts +190 -132
  171. package/src/dataStoreContext.ts +22 -4
  172. package/src/gc/garbageCollection.ts +15 -10
  173. package/src/gc/gcDefinitions.ts +7 -2
  174. package/src/gc/gcHelpers.ts +18 -6
  175. package/src/gc/gcTelemetry.ts +20 -8
  176. package/src/metadata.ts +11 -1
  177. package/src/opLifecycle/README.md +0 -8
  178. package/src/opLifecycle/batchManager.ts +46 -16
  179. package/src/opLifecycle/definitions.ts +1 -1
  180. package/src/opLifecycle/index.ts +8 -1
  181. package/src/opLifecycle/opCompressor.ts +12 -8
  182. package/src/opLifecycle/opGroupingManager.ts +14 -11
  183. package/src/opLifecycle/opSplitter.ts +10 -6
  184. package/src/opLifecycle/outbox.ts +64 -26
  185. package/src/opLifecycle/remoteMessageProcessor.ts +24 -8
  186. package/src/packageVersion.ts +1 -1
  187. package/src/pendingStateManager.ts +173 -74
  188. package/src/scheduleManager.ts +6 -2
  189. package/src/summary/README.md +81 -0
  190. package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
  191. package/src/summary/summaryFormat.ts +3 -1
  192. package/src/summary/summaryFormats.md +69 -8
  193. package/tsconfig.json +0 -1
  194. package/src/summary/images/appTree.png +0 -0
  195. package/src/summary/images/protocolAndAppTree.png +0 -0
  196. 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,
@@ -169,6 +169,7 @@ import {
169
169
  } from "./messageTypes.js";
170
170
  import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js";
171
171
  import {
172
+ BatchId,
172
173
  BatchMessage,
173
174
  IBatch,
174
175
  IBatchCheckpoint,
@@ -181,7 +182,7 @@ import {
181
182
  } from "./opLifecycle/index.js";
182
183
  import { pkgVersion } from "./packageVersion.js";
183
184
  import {
184
- IPendingBatchMessage,
185
+ PendingMessageResubmitData,
185
186
  IPendingLocalState,
186
187
  PendingStateManager,
187
188
  } from "./pendingStateManager.js";
@@ -668,7 +669,7 @@ export const makeLegacySendBatchFn =
668
669
  (batch: IBatch) => {
669
670
  // Default to negative one to match Container.submitBatch behavior
670
671
  let clientSequenceNumber: number = -1;
671
- for (const message of batch.content) {
672
+ for (const message of batch.messages) {
672
673
  clientSequenceNumber = submitFn(
673
674
  MessageType.Operation,
674
675
  // For back-compat (submitFn only works on deserialized content)
@@ -693,7 +694,7 @@ export const makeLegacySendBatchFn =
693
694
  type MessageWithContext = {
694
695
  local: boolean;
695
696
  savedOp?: boolean;
696
- batchStartCsn: number;
697
+ localOpMetadata?: unknown;
697
698
  } & (
698
699
  | {
699
700
  message: InboundSequencedContainerRuntimeMessage;
@@ -988,11 +989,7 @@ export class ContainerRuntime
988
989
  }
989
990
  };
990
991
 
991
- const disableCompression = mc.config.getBoolean(
992
- "Fluid.ContainerRuntime.CompressionDisabled",
993
- );
994
992
  const compressionLz4 =
995
- disableCompression !== true &&
996
993
  compressionOptions.minimumBatchSizeInBytes !== Infinity &&
997
994
  compressionOptions.compressionAlgorithm === "lz4";
998
995
 
@@ -1012,9 +1009,7 @@ export class ContainerRuntime
1012
1009
  },
1013
1010
  );
1014
1011
 
1015
- const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {
1016
- disableCompression,
1017
- };
1012
+ const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {};
1018
1013
 
1019
1014
  const runtime = new containerRuntimeCtor(
1020
1015
  context,
@@ -1360,6 +1355,13 @@ export class ContainerRuntime
1360
1355
  */
1361
1356
  private readonly loadedFromVersionId: string | undefined;
1362
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
+
1363
1365
  /**
1364
1366
  * It a cache for holding mapping for loading groupIds with its snapshot from the service. Add expiry policy of 1 minute.
1365
1367
  * Starting with 1 min and based on recorded usage we can tweak it later on.
@@ -1512,9 +1514,6 @@ export class ContainerRuntime
1512
1514
  this.disableAttachReorder = this.mc.config.getBoolean(
1513
1515
  "Fluid.ContainerRuntime.disableAttachOpReorder",
1514
1516
  );
1515
- const disableChunking = this.mc.config.getBoolean(
1516
- "Fluid.ContainerRuntime.CompressionChunkingDisabled",
1517
- );
1518
1517
 
1519
1518
  const opGroupingManager = new OpGroupingManager(
1520
1519
  {
@@ -1531,7 +1530,7 @@ export class ContainerRuntime
1531
1530
  const opSplitter = new OpSplitter(
1532
1531
  chunks,
1533
1532
  this.submitBatchFn,
1534
- disableChunking === true ? Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1533
+ runtimeOptions.chunkSizeInBytes,
1535
1534
  runtimeOptions.maxBatchSizeInBytes,
1536
1535
  this.mc.logger,
1537
1536
  );
@@ -1549,10 +1548,6 @@ export class ContainerRuntime
1549
1548
  clientId: () => this.clientId,
1550
1549
  close: this.closeFn,
1551
1550
  connected: () => this.connected,
1552
- reSubmit: (message: IPendingBatchMessage) => {
1553
- this.reSubmit(message);
1554
- this.flush();
1555
- },
1556
1551
  reSubmitBatch: this.reSubmitBatch.bind(this),
1557
1552
  isActiveConnection: () => this.innerDeltaManager.active,
1558
1553
  isAttached: () => this.attachState !== AttachState.Detached,
@@ -1665,6 +1660,10 @@ export class ContainerRuntime
1665
1660
 
1666
1661
  const parentContext = wrapContext(this);
1667
1662
 
1663
+ if (snapshotWithContents !== undefined) {
1664
+ this.isSnapshotInstanceOfISnapshot = true;
1665
+ }
1666
+
1668
1667
  // Due to a mismatch between different layers in terms of
1669
1668
  // what is the interface of passing signals, we need the
1670
1669
  // downstream stores to wrap the signal.
@@ -1921,7 +1920,6 @@ export class ContainerRuntime
1921
1920
  sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
1922
1921
  featureGates: JSON.stringify({
1923
1922
  ...featureGatesForTelemetry,
1924
- disableChunking,
1925
1923
  disableAttachReorder: this.disableAttachReorder,
1926
1924
  disablePartialFlush,
1927
1925
  closeSummarizerDelayOverride,
@@ -2167,9 +2165,13 @@ export class ContainerRuntime
2167
2165
  let childTree = snapshotTree;
2168
2166
  for (const part of pathParts) {
2169
2167
  if (hasIsolatedChannels) {
2170
- 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]!;
2171
2171
  }
2172
- 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]!;
2173
2175
  }
2174
2176
  return childTree;
2175
2177
  }
@@ -2221,7 +2223,9 @@ export class ContainerRuntime
2221
2223
  }
2222
2224
 
2223
2225
  if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
2224
- const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
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]!);
2225
2229
  return blob
2226
2230
  ? {
2227
2231
  status: 200,
@@ -2618,34 +2622,38 @@ export class ContainerRuntime
2618
2622
  // but will not modify the contents object (likely it will replace it on the message).
2619
2623
  const messageCopy = { ...messageArg };
2620
2624
  const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2621
- const processResult = this.remoteMessageProcessor.process(messageCopy);
2622
- if (processResult === undefined) {
2623
- // This means the incoming message is an incomplete part of a message or batch
2624
- // and we need to process more messages before the rest of the system can understand it.
2625
- return;
2626
- }
2627
- for (const message of processResult.messages) {
2628
- const msg: MessageWithContext = modernRuntimeMessage
2629
- ? {
2630
- // Cast it since we expect it to be this based on modernRuntimeMessage computation above.
2631
- // There is nothing really ensuring that anytime original message.type is Operation that
2632
- // the result messages will be so. In the end modern bool being true only directs to
2633
- // throw error if ultimately unrecognized without compat details saying otherwise.
2634
- message: message as InboundSequencedContainerRuntimeMessage,
2635
- local,
2636
- modernRuntimeMessage,
2637
- batchStartCsn: processResult.batchStartCsn,
2638
- }
2639
- : // Unrecognized message will be ignored.
2640
- {
2641
- message,
2642
- local,
2643
- modernRuntimeMessage,
2644
- batchStartCsn: processResult.batchStartCsn,
2645
- };
2646
- msg.savedOp = savedOp;
2647
-
2648
- // 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
+ };
2649
2657
  this.ensureNoDataModelChanges(() => this.processCore(msg));
2650
2658
  }
2651
2659
  }
@@ -2656,7 +2664,7 @@ export class ContainerRuntime
2656
2664
  * Direct the message to the correct subsystem for processing, and implement other side effects
2657
2665
  */
2658
2666
  private processCore(messageWithContext: MessageWithContext) {
2659
- const { message, local } = messageWithContext;
2667
+ const { message, local, localOpMetadata } = messageWithContext;
2660
2668
 
2661
2669
  // Intercept to reduce minimum sequence number to the delta manager's minimum sequence number.
2662
2670
  // Sequence numbers are not guaranteed to follow any sort of order. Re-entrancy is one of those situations
@@ -2676,23 +2684,12 @@ export class ContainerRuntime
2676
2684
  this._processedClientSequenceNumber = message.clientSequenceNumber;
2677
2685
 
2678
2686
  try {
2679
- // See commit that added this assert for more details.
2680
- // These calls should be made for all but chunked ops:
2681
- // 1) this.pendingStateManager.processPendingLocalMessage() below
2682
- // 2) this.resetReconnectCount() below
2687
+ // RemoteMessageProcessor would have already reconstituted Chunked Ops into the original op type
2683
2688
  assert(
2684
2689
  message.type !== ContainerMessageType.ChunkedOp,
2685
2690
  0x93b /* we should never get here with chunked ops */,
2686
2691
  );
2687
2692
 
2688
- let localOpMetadata: unknown;
2689
- if (local && messageWithContext.modernRuntimeMessage) {
2690
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(
2691
- messageWithContext.message,
2692
- messageWithContext.batchStartCsn,
2693
- );
2694
- }
2695
-
2696
2693
  // If there are no more pending messages after processing a local message,
2697
2694
  // the document is no longer dirty.
2698
2695
  if (!this.hasPendingMessages()) {
@@ -2888,14 +2885,16 @@ export class ContainerRuntime
2888
2885
  /**
2889
2886
  * Flush the pending ops manually.
2890
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
2891
2890
  */
2892
- private flush(): void {
2891
+ private flush(resubmittingBatchId?: BatchId): void {
2893
2892
  assert(
2894
2893
  this._orderSequentiallyCalls === 0,
2895
2894
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */,
2896
2895
  );
2897
2896
 
2898
- this.outbox.flush();
2897
+ this.outbox.flush(resubmittingBatchId);
2899
2898
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
2900
2899
  }
2901
2900
 
@@ -2909,7 +2908,7 @@ export class ContainerRuntime
2909
2908
  // Note: we are not touching any batches other than mainBatch here, for two reasons:
2910
2909
  // 1. It would not help, as other batches are flushed independently from main batch.
2911
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.
2912
- checkpoint = this.outbox.checkpoint().mainBatch;
2911
+ checkpoint = this.outbox.getBatchCheckpoints().mainBatch;
2913
2912
  }
2914
2913
  try {
2915
2914
  this._orderSequentiallyCalls++;
@@ -3052,7 +3051,7 @@ export class ContainerRuntime
3052
3051
  }
3053
3052
 
3054
3053
  /**
3055
- * 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.
3056
3055
  */
3057
3056
  private currentlyBatching() {
3058
3057
  return this.flushMode !== FlushMode.Immediate || this._orderSequentiallyCalls !== 0;
@@ -3552,7 +3551,7 @@ export class ContainerRuntime
3552
3551
  summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
3553
3552
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
3554
3553
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
3555
- const lastAck = this.summaryCollection.latestAck;
3554
+ const lastAckedContext = this.lastAckedSummaryContext;
3556
3555
 
3557
3556
  const startSummaryResult = this.summarizerNode.startSummary(
3558
3557
  summaryRefSeqNum,
@@ -3619,10 +3618,10 @@ export class ContainerRuntime
3619
3618
  0x395 /* it's one and the same thing */,
3620
3619
  );
3621
3620
 
3622
- if (lastAck !== this.summaryCollection.latestAck) {
3621
+ if (lastAckedContext !== this.lastAckedSummaryContext) {
3623
3622
  return {
3624
3623
  continue: false,
3625
- error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
3624
+ error: `Last summary changed while summarizing. ${this.lastAckedSummaryContext} !== ${lastAckedContext}`,
3626
3625
  };
3627
3626
  }
3628
3627
  return { continue: true };
@@ -3694,7 +3693,9 @@ export class ContainerRuntime
3694
3693
  // Counting dataStores and handles
3695
3694
  // Because handles are unchanged dataStores in the current logic,
3696
3695
  // summarized dataStore count is total dataStore count minus handle count
3697
- 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]!;
3698
3699
 
3699
3700
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
3700
3701
  const handleCount = Object.values(dataStoreTree.tree).filter(
@@ -3730,18 +3731,11 @@ export class ContainerRuntime
3730
3731
  };
3731
3732
  }
3732
3733
 
3733
- const summaryContext =
3734
- lastAck === undefined
3735
- ? {
3736
- proposalHandle: undefined,
3737
- ackHandle: this.loadedFromVersionId,
3738
- referenceSequenceNumber: summaryRefSeqNum,
3739
- }
3740
- : {
3741
- proposalHandle: lastAck.summaryOp.contents.handle,
3742
- ackHandle: lastAck.summaryAck.contents.handle,
3743
- referenceSequenceNumber: summaryRefSeqNum,
3744
- };
3734
+ const summaryContext: ISummaryContext = {
3735
+ proposalHandle: this.lastAckedSummaryContext?.proposalHandle ?? undefined,
3736
+ ackHandle: this.lastAckedSummaryContext?.ackHandle ?? this.loadedFromVersionId,
3737
+ referenceSequenceNumber: summaryRefSeqNum,
3738
+ };
3745
3739
 
3746
3740
  let handle: string;
3747
3741
  try {
@@ -4032,7 +4026,9 @@ export class ContainerRuntime
4032
4026
  this.outbox.submit(message);
4033
4027
  }
4034
4028
 
4035
- 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) {
4036
4032
  this.flush();
4037
4033
  } else {
4038
4034
  this.scheduleFlush();
@@ -4113,16 +4109,22 @@ export class ContainerRuntime
4113
4109
  }
4114
4110
  }
4115
4111
 
4116
- private reSubmitBatch(batch: IPendingBatchMessage[]) {
4112
+ private reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId) {
4117
4113
  this.orderSequentially(() => {
4118
4114
  for (const message of batch) {
4119
4115
  this.reSubmit(message);
4120
4116
  }
4121
4117
  });
4122
- 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);
4123
4125
  }
4124
4126
 
4125
- private reSubmit(message: IPendingBatchMessage) {
4127
+ private reSubmit(message: PendingMessageResubmitData) {
4126
4128
  // Need to parse from string for back-compat
4127
4129
  const containerRuntimeMessage = this.parseLocalOpContent(message.content);
4128
4130
  this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
@@ -4225,84 +4227,140 @@ export class ContainerRuntime
4225
4227
  const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
4226
4228
  // proposalHandle is always passed from RunningSummarizer.
4227
4229
  assert(proposalHandle !== undefined, 0x766 /* proposalHandle should be available */);
4228
- const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
4229
4230
  const result = await this.summarizerNode.refreshLatestSummary(
4230
4231
  proposalHandle,
4231
4232
  summaryRefSeq,
4232
4233
  );
4233
4234
 
4235
+ /* eslint-disable jsdoc/check-indentation */
4234
4236
  /**
4235
- * When refreshing a summary ack, this check indicates a new ack of a summary that is newer than the
4236
- * current summary that is tracked, but this summarizer runtime did not produce/track that summary. Thus
4237
- * it needs to refresh its state. Today refresh is done by fetching the latest snapshot to update the cache
4238
- * 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.
4239
4252
  */
4240
- if (!result.isSummaryTracked && result.isSummaryNewer) {
4241
- await this.fetchLatestSnapshotAndClose(
4242
- summaryLogger,
4243
- {
4244
- eventName: "RefreshLatestSummaryAckFetch",
4245
- ackHandle,
4246
- targetSequenceNumber: summaryRefSeq,
4247
- },
4248
- readAndParseBlob,
4249
- );
4253
+ /* eslint-enable jsdoc/check-indentation */
4254
+ if (!result.isSummaryTracked) {
4255
+ if (result.isSummaryNewer) {
4256
+ await this.fetchLatestSnapshotAndMaybeClose(summaryRefSeq, ackHandle, summaryLogger);
4257
+ }
4250
4258
  return;
4251
4259
  }
4252
4260
 
4253
4261
  // Notify the garbage collector so it can update its latest summary state.
4254
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
+ };
4255
4270
  }
4256
4271
 
4257
4272
  /**
4258
- * Fetches the latest snapshot from storage and closes the container. This is done in cases where
4259
- * the last known snapshot is older than the latest one. This will ensure that the latest snapshot
4260
- * 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.
4261
4278
  */
4262
- private async fetchLatestSnapshotAndClose(
4279
+ private async fetchLatestSnapshotAndMaybeClose(
4280
+ targetRefSeq: number,
4281
+ targetAckHandle: string,
4263
4282
  logger: ITelemetryLoggerExt,
4264
- event: ITelemetryGenericEventExt,
4265
- readAndParseBlob: ReadAndParseBlob,
4266
4283
  ) {
4267
- await PerformanceEvent.timedExecAsync(
4284
+ const fetchedSnapshotRefSeq = await PerformanceEvent.timedExecAsync(
4268
4285
  logger,
4269
- event,
4286
+ { eventName: "RefreshLatestSummaryAckFetch" },
4270
4287
  async (perfEvent: {
4271
4288
  end: (arg0: {
4272
- getVersionDuration?: number | undefined;
4273
- getSnapshotDuration?: number | undefined;
4274
- snapshotRefSeq?: number | undefined;
4275
- 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
+ };
4276
4298
  }) => void;
4277
4299
  }) => {
4278
- const stats: {
4300
+ const props: {
4279
4301
  getVersionDuration?: number;
4280
4302
  getSnapshotDuration?: number;
4281
4303
  snapshotRefSeq?: number;
4282
4304
  snapshotVersion?: string;
4283
- } = {};
4305
+ newerSnapshotPresent?: boolean | undefined;
4306
+ targetRefSeq?: number | undefined;
4307
+ targetAckHandle?: string | undefined;
4308
+ } = { targetRefSeq, targetAckHandle };
4284
4309
  const trace = Trace.start();
4285
4310
 
4286
- const versions = await this.storage.getVersions(
4287
- null,
4288
- 1,
4289
- "prefetchLatestSummaryBeforeClose",
4290
- FetchSource.noCache,
4291
- );
4292
- assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
4293
- 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
+ }
4294
4345
 
4295
- const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
4296
- assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
4297
- stats.getSnapshotDuration = trace.trace().duration;
4298
- const latestSnapshotRefSeq = await seqFromTree(maybeSnapshot, readAndParseBlob);
4299
- stats.snapshotRefSeq = latestSnapshotRefSeq;
4300
- 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;
4301
4351
 
4302
- perfEvent.end(stats);
4352
+ perfEvent.end({ details: props });
4353
+ return snapshotRefSeq;
4303
4354
  },
4304
4355
  );
4305
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
+
4306
4364
  await delay(this.closeSummarizerDelayMs);
4307
4365
  this._summarizer?.stop("latestSummaryStateStale");
4308
4366
  this.disposeFn();
@@ -59,7 +59,6 @@ import {
59
59
  isSnapshotFetchRequiredForLoadingGroupId,
60
60
  } from "@fluidframework/runtime-utils/internal";
61
61
  import {
62
- DataCorruptionError,
63
62
  DataProcessingError,
64
63
  LoggingError,
65
64
  MonitoringContext,
@@ -305,6 +304,13 @@ export abstract class FluidDataStoreContext
305
304
  return this._isInMemoryRoot;
306
305
  }
307
306
 
307
+ /**
308
+ * Returns the count of pending messages that are stored until the data store is realized.
309
+ */
310
+ public get pendingCount(): number {
311
+ return this.pending?.length ?? 0;
312
+ }
313
+
308
314
  protected registry: IFluidDataStoreRegistry | undefined;
309
315
 
310
316
  protected detachedRuntimeCreation = false;
@@ -567,8 +573,10 @@ export abstract class FluidDataStoreContext
567
573
  // "verifyNotClosed" which logs tombstone errors. Throw error if tombstoned and throwing on load is configured.
568
574
  this.verifyNotClosed("process", false /* checkTombstone */, safeTelemetryProps);
569
575
  if (this.tombstoned && this.gcThrowOnTombstoneUsage) {
570
- throw new DataCorruptionError(
576
+ throw DataProcessingError.create(
571
577
  "Context is tombstoned! Call site [process]",
578
+ "process",
579
+ undefined /* sequencedMessage */,
572
580
  safeTelemetryProps,
573
581
  );
574
582
  }
@@ -961,7 +969,12 @@ export abstract class FluidDataStoreContext
961
969
  ) {
962
970
  if (this.deleted) {
963
971
  const messageString = `Context is deleted! Call site [${callSite}]`;
964
- const error = new DataCorruptionError(messageString, safeTelemetryProps);
972
+ const error = DataProcessingError.create(
973
+ messageString,
974
+ callSite,
975
+ undefined /* sequencedMessage */,
976
+ safeTelemetryProps,
977
+ );
965
978
  this.mc.logger.sendErrorEvent(
966
979
  {
967
980
  eventName: "GC_Deleted_DataStore_Changed",
@@ -979,7 +992,12 @@ export abstract class FluidDataStoreContext
979
992
 
980
993
  if (checkTombstone && this.tombstoned) {
981
994
  const messageString = `Context is tombstoned! Call site [${callSite}]`;
982
- const error = new DataCorruptionError(messageString, safeTelemetryProps);
995
+ const error = DataProcessingError.create(
996
+ messageString,
997
+ callSite,
998
+ undefined /* sequencedMessage */,
999
+ safeTelemetryProps,
1000
+ );
983
1001
 
984
1002
  sendGCUnexpectedUsageEvent(
985
1003
  this.mc,