@fluidframework/container-runtime 2.1.0-276985 → 2.1.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 (199) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +71 -18
  3. package/api-extractor/api-extractor.current.json +5 -0
  4. package/api-extractor/api-extractor.legacy.json +1 -1
  5. package/api-extractor.json +1 -1
  6. package/api-report/container-runtime.legacy.public.api.md +9 -0
  7. package/container-runtime.test-files.tar +0 -0
  8. package/dist/blobManager/blobManager.d.ts +10 -0
  9. package/dist/blobManager/blobManager.d.ts.map +1 -1
  10. package/dist/blobManager/blobManager.js +19 -0
  11. package/dist/blobManager/blobManager.js.map +1 -1
  12. package/dist/channelCollection.d.ts +1 -1
  13. package/dist/channelCollection.d.ts.map +1 -1
  14. package/dist/channelCollection.js +40 -8
  15. package/dist/channelCollection.js.map +1 -1
  16. package/dist/containerRuntime.d.ts +14 -5
  17. package/dist/containerRuntime.d.ts.map +1 -1
  18. package/dist/containerRuntime.js +151 -99
  19. package/dist/containerRuntime.js.map +1 -1
  20. package/dist/dataStoreContext.d.ts +4 -0
  21. package/dist/dataStoreContext.d.ts.map +1 -1
  22. package/dist/dataStoreContext.js +9 -3
  23. package/dist/dataStoreContext.js.map +1 -1
  24. package/dist/gc/garbageCollection.d.ts +1 -1
  25. package/dist/gc/garbageCollection.d.ts.map +1 -1
  26. package/dist/gc/garbageCollection.js +14 -8
  27. package/dist/gc/garbageCollection.js.map +1 -1
  28. package/dist/gc/gcDefinitions.d.ts +4 -2
  29. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  30. package/dist/gc/gcDefinitions.js.map +1 -1
  31. package/dist/gc/gcHelpers.d.ts.map +1 -1
  32. package/dist/gc/gcHelpers.js +12 -0
  33. package/dist/gc/gcHelpers.js.map +1 -1
  34. package/dist/gc/gcTelemetry.d.ts +3 -2
  35. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  36. package/dist/gc/gcTelemetry.js +6 -6
  37. package/dist/gc/gcTelemetry.js.map +1 -1
  38. package/dist/legacy.d.ts +1 -1
  39. package/dist/metadata.d.ts +7 -1
  40. package/dist/metadata.d.ts.map +1 -1
  41. package/dist/metadata.js +6 -0
  42. package/dist/metadata.js.map +1 -1
  43. package/dist/opLifecycle/batchManager.d.ts +8 -1
  44. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  45. package/dist/opLifecycle/batchManager.js +37 -16
  46. package/dist/opLifecycle/batchManager.js.map +1 -1
  47. package/dist/opLifecycle/definitions.d.ts +1 -1
  48. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  49. package/dist/opLifecycle/definitions.js.map +1 -1
  50. package/dist/opLifecycle/index.d.ts +2 -2
  51. package/dist/opLifecycle/index.d.ts.map +1 -1
  52. package/dist/opLifecycle/index.js +3 -1
  53. package/dist/opLifecycle/index.js.map +1 -1
  54. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  55. package/dist/opLifecycle/opCompressor.js +12 -8
  56. package/dist/opLifecycle/opCompressor.js.map +1 -1
  57. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  58. package/dist/opLifecycle/opGroupingManager.js +14 -11
  59. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  60. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  61. package/dist/opLifecycle/opSplitter.js +11 -6
  62. package/dist/opLifecycle/opSplitter.js.map +1 -1
  63. package/dist/opLifecycle/outbox.d.ts +22 -6
  64. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  65. package/dist/opLifecycle/outbox.js +43 -21
  66. package/dist/opLifecycle/outbox.js.map +1 -1
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts +10 -8
  68. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  69. package/dist/opLifecycle/remoteMessageProcessor.js +39 -15
  70. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  71. package/dist/packageVersion.d.ts +1 -1
  72. package/dist/packageVersion.d.ts.map +1 -1
  73. package/dist/packageVersion.js +1 -1
  74. package/dist/packageVersion.js.map +1 -1
  75. package/dist/pendingStateManager.d.ts +37 -13
  76. package/dist/pendingStateManager.d.ts.map +1 -1
  77. package/dist/pendingStateManager.js +95 -45
  78. package/dist/pendingStateManager.js.map +1 -1
  79. package/dist/public.d.ts +1 -1
  80. package/dist/scheduleManager.js +4 -0
  81. package/dist/scheduleManager.js.map +1 -1
  82. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  83. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  84. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  85. package/dist/summary/summaryFormat.d.ts.map +1 -1
  86. package/dist/summary/summaryFormat.js +4 -1
  87. package/dist/summary/summaryFormat.js.map +1 -1
  88. package/internal.d.ts +1 -1
  89. package/legacy.d.ts +1 -1
  90. package/lib/blobManager/blobManager.d.ts +10 -0
  91. package/lib/blobManager/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager/blobManager.js +19 -0
  93. package/lib/blobManager/blobManager.js.map +1 -1
  94. package/lib/channelCollection.d.ts +1 -1
  95. package/lib/channelCollection.d.ts.map +1 -1
  96. package/lib/channelCollection.js +40 -8
  97. package/lib/channelCollection.js.map +1 -1
  98. package/lib/containerRuntime.d.ts +14 -5
  99. package/lib/containerRuntime.d.ts.map +1 -1
  100. package/lib/containerRuntime.js +152 -100
  101. package/lib/containerRuntime.js.map +1 -1
  102. package/lib/dataStoreContext.d.ts +4 -0
  103. package/lib/dataStoreContext.d.ts.map +1 -1
  104. package/lib/dataStoreContext.js +10 -4
  105. package/lib/dataStoreContext.js.map +1 -1
  106. package/lib/gc/garbageCollection.d.ts +1 -1
  107. package/lib/gc/garbageCollection.d.ts.map +1 -1
  108. package/lib/gc/garbageCollection.js +14 -8
  109. package/lib/gc/garbageCollection.js.map +1 -1
  110. package/lib/gc/gcDefinitions.d.ts +4 -2
  111. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  112. package/lib/gc/gcDefinitions.js.map +1 -1
  113. package/lib/gc/gcHelpers.d.ts.map +1 -1
  114. package/lib/gc/gcHelpers.js +12 -0
  115. package/lib/gc/gcHelpers.js.map +1 -1
  116. package/lib/gc/gcTelemetry.d.ts +3 -2
  117. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  118. package/lib/gc/gcTelemetry.js +6 -6
  119. package/lib/gc/gcTelemetry.js.map +1 -1
  120. package/lib/legacy.d.ts +1 -1
  121. package/lib/metadata.d.ts +7 -1
  122. package/lib/metadata.d.ts.map +1 -1
  123. package/lib/metadata.js +4 -1
  124. package/lib/metadata.js.map +1 -1
  125. package/lib/opLifecycle/batchManager.d.ts +8 -1
  126. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  127. package/lib/opLifecycle/batchManager.js +35 -15
  128. package/lib/opLifecycle/batchManager.js.map +1 -1
  129. package/lib/opLifecycle/definitions.d.ts +1 -1
  130. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  131. package/lib/opLifecycle/definitions.js.map +1 -1
  132. package/lib/opLifecycle/index.d.ts +2 -2
  133. package/lib/opLifecycle/index.d.ts.map +1 -1
  134. package/lib/opLifecycle/index.js +2 -2
  135. package/lib/opLifecycle/index.js.map +1 -1
  136. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  137. package/lib/opLifecycle/opCompressor.js +12 -8
  138. package/lib/opLifecycle/opCompressor.js.map +1 -1
  139. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  140. package/lib/opLifecycle/opGroupingManager.js +14 -11
  141. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  142. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSplitter.js +11 -6
  144. package/lib/opLifecycle/opSplitter.js.map +1 -1
  145. package/lib/opLifecycle/outbox.d.ts +22 -6
  146. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  147. package/lib/opLifecycle/outbox.js +44 -22
  148. package/lib/opLifecycle/outbox.js.map +1 -1
  149. package/lib/opLifecycle/remoteMessageProcessor.d.ts +10 -8
  150. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  151. package/lib/opLifecycle/remoteMessageProcessor.js +37 -14
  152. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  153. package/lib/packageVersion.d.ts +1 -1
  154. package/lib/packageVersion.d.ts.map +1 -1
  155. package/lib/packageVersion.js +1 -1
  156. package/lib/packageVersion.js.map +1 -1
  157. package/lib/pendingStateManager.d.ts +37 -13
  158. package/lib/pendingStateManager.d.ts.map +1 -1
  159. package/lib/pendingStateManager.js +95 -45
  160. package/lib/pendingStateManager.js.map +1 -1
  161. package/lib/public.d.ts +1 -1
  162. package/lib/scheduleManager.js +4 -0
  163. package/lib/scheduleManager.js.map +1 -1
  164. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  165. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  166. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  167. package/lib/summary/summaryFormat.d.ts.map +1 -1
  168. package/lib/summary/summaryFormat.js +4 -1
  169. package/lib/summary/summaryFormat.js.map +1 -1
  170. package/package.json +46 -31
  171. package/src/blobManager/blobManager.ts +19 -0
  172. package/src/channelCollection.ts +48 -11
  173. package/src/containerRuntime.ts +203 -133
  174. package/src/dataStoreContext.ts +22 -4
  175. package/src/gc/garbageCollection.ts +15 -10
  176. package/src/gc/gcDefinitions.ts +7 -2
  177. package/src/gc/gcHelpers.ts +18 -6
  178. package/src/gc/gcTelemetry.ts +20 -8
  179. package/src/metadata.ts +11 -1
  180. package/src/opLifecycle/README.md +0 -8
  181. package/src/opLifecycle/batchManager.ts +49 -16
  182. package/src/opLifecycle/definitions.ts +1 -1
  183. package/src/opLifecycle/index.ts +13 -2
  184. package/src/opLifecycle/opCompressor.ts +12 -8
  185. package/src/opLifecycle/opGroupingManager.ts +14 -11
  186. package/src/opLifecycle/opSplitter.ts +10 -6
  187. package/src/opLifecycle/outbox.ts +64 -26
  188. package/src/opLifecycle/remoteMessageProcessor.ts +56 -17
  189. package/src/packageVersion.ts +1 -1
  190. package/src/pendingStateManager.ts +173 -74
  191. package/src/scheduleManager.ts +6 -2
  192. package/src/summary/README.md +81 -0
  193. package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
  194. package/src/summary/summaryFormat.ts +3 -1
  195. package/src/summary/summaryFormats.md +69 -8
  196. package/tsconfig.json +0 -1
  197. package/src/summary/images/appTree.png +0 -0
  198. package/src/summary/images/protocolAndAppTree.png +0 -0
  199. package/src/summary/images/summaryTree.png +0 -0
@@ -25,7 +25,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
25
25
  import { DeltaManagerPendingOpsProxy, DeltaManagerSummarizerProxy, } from "./deltaManagerProxies.js";
26
26
  import { GCNodeType, GarbageCollector, gcGenerationOptionName, } from "./gc/index.js";
27
27
  import { ContainerMessageType, } from "./messageTypes.js";
28
- import { OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
28
+ import { ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
29
29
  import { pkgVersion } from "./packageVersion.js";
30
30
  import { PendingStateManager, } from "./pendingStateManager.js";
31
31
  import { ScheduleManager } from "./scheduleManager.js";
@@ -161,7 +161,7 @@ export function getDeviceSpec() {
161
161
  export const makeLegacySendBatchFn = (submitFn, deltaManager) => (batch) => {
162
162
  // Default to negative one to match Container.submitBatch behavior
163
163
  let clientSequenceNumber = -1;
164
- for (const message of batch.content) {
164
+ for (const message of batch.messages) {
165
165
  clientSequenceNumber = submitFn(MessageType.Operation,
166
166
  // For back-compat (submitFn only works on deserialized content)
167
167
  message.contents === undefined ? undefined : JSON.parse(message.contents), true, // batch
@@ -375,9 +375,7 @@ export class ContainerRuntime extends TypedEventEmitter {
375
375
  return createIdCompressor(compressorLogger);
376
376
  }
377
377
  };
378
- const disableCompression = mc.config.getBoolean("Fluid.ContainerRuntime.CompressionDisabled");
379
- const compressionLz4 = disableCompression !== true &&
380
- compressionOptions.minimumBatchSizeInBytes !== Infinity &&
378
+ const compressionLz4 = compressionOptions.minimumBatchSizeInBytes !== Infinity &&
381
379
  compressionOptions.compressionAlgorithm === "lz4";
382
380
  const documentSchemaController = new DocumentsSchemaController(existing, protocolSequenceNumber, metadata?.documentSchema, {
383
381
  explicitSchemaControl,
@@ -388,9 +386,7 @@ export class ContainerRuntime extends TypedEventEmitter {
388
386
  }, (schema) => {
389
387
  runtime.onSchemaChange(schema);
390
388
  });
391
- const featureGatesForTelemetry = {
392
- disableCompression,
393
- };
389
+ const featureGatesForTelemetry = {};
394
390
  const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], {
395
391
  summaryOptions,
396
392
  gcOptions,
@@ -660,14 +656,13 @@ export class ContainerRuntime extends TypedEventEmitter {
660
656
  });
661
657
  this.telemetryDocumentId = metadata?.telemetryDocumentId ?? uuid();
662
658
  this.disableAttachReorder = this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder");
663
- const disableChunking = this.mc.config.getBoolean("Fluid.ContainerRuntime.CompressionChunkingDisabled");
664
659
  const opGroupingManager = new OpGroupingManager({
665
660
  groupedBatchingEnabled: this.groupedBatchingEnabled,
666
661
  opCountThreshold: this.mc.config.getNumber("Fluid.ContainerRuntime.GroupedBatchingOpCount") ?? 2,
667
662
  reentrantBatchGroupingEnabled: this.mc.config.getBoolean("Fluid.ContainerRuntime.GroupedBatchingReentrancy") ??
668
663
  true,
669
664
  }, this.mc.logger);
670
- const opSplitter = new OpSplitter(chunks, this.submitBatchFn, disableChunking === true ? Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes, runtimeOptions.maxBatchSizeInBytes, this.mc.logger);
665
+ const opSplitter = new OpSplitter(chunks, this.submitBatchFn, runtimeOptions.chunkSizeInBytes, runtimeOptions.maxBatchSizeInBytes, this.mc.logger);
671
666
  this.remoteMessageProcessor = new RemoteMessageProcessor(opSplitter, new OpDecompressor(this.mc.logger), opGroupingManager);
672
667
  const pendingRuntimeState = pendingLocalState;
673
668
  this.pendingStateManager = new PendingStateManager({
@@ -675,10 +670,6 @@ export class ContainerRuntime extends TypedEventEmitter {
675
670
  clientId: () => this.clientId,
676
671
  close: this.closeFn,
677
672
  connected: () => this.connected,
678
- reSubmit: (message) => {
679
- this.reSubmit(message);
680
- this.flush();
681
- },
682
673
  reSubmitBatch: this.reSubmitBatch.bind(this),
683
674
  isActiveConnection: () => this.innerDeltaManager.active,
684
675
  isAttached: () => this.attachState !== AttachState.Detached,
@@ -761,6 +752,9 @@ export class ContainerRuntime extends TypedEventEmitter {
761
752
  this.summarizerNode.updateBaseSummaryState(baseSnapshot);
762
753
  }
763
754
  const parentContext = wrapContext(this);
755
+ if (snapshotWithContents !== undefined) {
756
+ this.isSnapshotInstanceOfISnapshot = true;
757
+ }
764
758
  // Due to a mismatch between different layers in terms of
765
759
  // what is the interface of passing signals, we need the
766
760
  // downstream stores to wrap the signal.
@@ -930,7 +924,6 @@ export class ContainerRuntime extends TypedEventEmitter {
930
924
  sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
931
925
  featureGates: JSON.stringify({
932
926
  ...featureGatesForTelemetry,
933
- disableChunking,
934
927
  disableAttachReorder: this.disableAttachReorder,
935
928
  disablePartialFlush,
936
929
  closeSummarizerDelayOverride,
@@ -1109,9 +1102,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1109
1102
  let childTree = snapshotTree;
1110
1103
  for (const part of pathParts) {
1111
1104
  if (hasIsolatedChannels) {
1112
- childTree = childTree?.trees[channelsTreeName];
1105
+ // TODO Why are we non null asserting here
1106
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1107
+ childTree = childTree.trees[channelsTreeName];
1113
1108
  }
1114
- childTree = childTree?.trees[part];
1109
+ // TODO Why are we non null asserting here
1110
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1111
+ childTree = childTree.trees[part];
1115
1112
  }
1116
1113
  return childTree;
1117
1114
  }
@@ -1158,6 +1155,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1158
1155
  return this.resolveHandle(requestParser.createSubRequest(1));
1159
1156
  }
1160
1157
  if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
1158
+ // TODO why are we non null asserting here?
1159
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1161
1160
  const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
1162
1161
  return blob
1163
1162
  ? {
@@ -1465,39 +1464,50 @@ export class ContainerRuntime extends TypedEventEmitter {
1465
1464
  // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
1466
1465
  // or something different, like a system message.
1467
1466
  const modernRuntimeMessage = messageArg.type === MessageType.Operation;
1467
+ const savedOp = messageArg.metadata?.savedOp;
1468
+ // There is some ancient back-compat code that we'd like to instrument
1469
+ // to understand if/when it is hit.
1470
+ const logLegacyCase = (codePath) => this.logger.sendTelemetryEvent({
1471
+ eventName: "LegacyMessageFormat",
1472
+ details: { codePath, type: messageArg.type },
1473
+ });
1468
1474
  // Do shallow copy of message, as the processing flow will modify it.
1469
1475
  // There might be multiple container instances receiving the same message.
1470
1476
  // We do not need to make a deep copy. Each layer will just replace message.contents itself,
1471
1477
  // but will not modify the contents object (likely it will replace it on the message).
1472
1478
  const messageCopy = { ...messageArg };
1473
- const savedOp = messageCopy.metadata?.savedOp;
1474
- const processResult = this.remoteMessageProcessor.process(messageCopy);
1475
- if (processResult === undefined) {
1476
- // This means the incoming message is an incomplete part of a message or batch
1477
- // and we need to process more messages before the rest of the system can understand it.
1478
- return;
1479
- }
1480
- for (const message of processResult.messages) {
1481
- const msg = modernRuntimeMessage
1482
- ? {
1483
- // Cast it since we expect it to be this based on modernRuntimeMessage computation above.
1484
- // There is nothing really ensuring that anytime original message.type is Operation that
1485
- // the result messages will be so. In the end modern bool being true only directs to
1486
- // throw error if ultimately unrecognized without compat details saying otherwise.
1487
- message: message,
1479
+ // We expect runtime messages to have JSON contents - deserialize it in place.
1480
+ ensureContentsDeserialized(messageCopy, modernRuntimeMessage, logLegacyCase);
1481
+ if (modernRuntimeMessage) {
1482
+ const processResult = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
1483
+ if (processResult === undefined) {
1484
+ // This means the incoming message is an incomplete part of a message or batch
1485
+ // and we need to process more messages before the rest of the system can understand it.
1486
+ return;
1487
+ }
1488
+ const batchStartCsn = processResult.batchStartCsn;
1489
+ const batch = processResult.messages;
1490
+ const messages = local
1491
+ ? this.pendingStateManager.processPendingLocalBatch(batch, batchStartCsn)
1492
+ : batch.map((message) => ({ message, localOpMetadata: undefined }));
1493
+ messages.forEach(({ message, localOpMetadata }) => {
1494
+ const msg = {
1495
+ message,
1488
1496
  local,
1489
1497
  modernRuntimeMessage,
1490
- batchStartCsn: processResult.batchStartCsn,
1491
- }
1492
- : // Unrecognized message will be ignored.
1493
- {
1494
- message,
1495
- local,
1496
- modernRuntimeMessage,
1497
- batchStartCsn: processResult.batchStartCsn,
1498
- };
1499
- msg.savedOp = savedOp;
1500
- // ensure that we observe any re-entrancy, and if needed, rebase ops
1498
+ savedOp,
1499
+ localOpMetadata,
1500
+ };
1501
+ this.ensureNoDataModelChanges(() => this.processCore(msg));
1502
+ });
1503
+ }
1504
+ else {
1505
+ const msg = {
1506
+ message: messageCopy,
1507
+ local,
1508
+ modernRuntimeMessage,
1509
+ savedOp,
1510
+ };
1501
1511
  this.ensureNoDataModelChanges(() => this.processCore(msg));
1502
1512
  }
1503
1513
  }
@@ -1505,7 +1515,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1505
1515
  * Direct the message to the correct subsystem for processing, and implement other side effects
1506
1516
  */
1507
1517
  processCore(messageWithContext) {
1508
- const { message, local } = messageWithContext;
1518
+ const { message, local, localOpMetadata } = messageWithContext;
1509
1519
  // Intercept to reduce minimum sequence number to the delta manager's minimum sequence number.
1510
1520
  // Sequence numbers are not guaranteed to follow any sort of order. Re-entrancy is one of those situations
1511
1521
  if (this.deltaManager.minimumSequenceNumber <
@@ -1519,15 +1529,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1519
1529
  this.scheduleManager.beforeOpProcessing(message);
1520
1530
  this._processedClientSequenceNumber = message.clientSequenceNumber;
1521
1531
  try {
1522
- // See commit that added this assert for more details.
1523
- // These calls should be made for all but chunked ops:
1524
- // 1) this.pendingStateManager.processPendingLocalMessage() below
1525
- // 2) this.resetReconnectCount() below
1532
+ // RemoteMessageProcessor would have already reconstituted Chunked Ops into the original op type
1526
1533
  assert(message.type !== ContainerMessageType.ChunkedOp, 0x93b /* we should never get here with chunked ops */);
1527
- let localOpMetadata;
1528
- if (local && messageWithContext.modernRuntimeMessage) {
1529
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(messageWithContext.message, messageWithContext.batchStartCsn);
1530
- }
1531
1534
  // If there are no more pending messages after processing a local message,
1532
1535
  // the document is no longer dirty.
1533
1536
  if (!this.hasPendingMessages()) {
@@ -1686,10 +1689,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1686
1689
  /**
1687
1690
  * Flush the pending ops manually.
1688
1691
  * This method is expected to be called at the end of a batch.
1692
+ * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
1693
+ * with the given Batch ID, which must be preserved
1689
1694
  */
1690
- flush() {
1695
+ flush(resubmittingBatchId) {
1691
1696
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1692
- this.outbox.flush();
1697
+ this.outbox.flush(resubmittingBatchId);
1693
1698
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1694
1699
  }
1695
1700
  /**
@@ -1702,7 +1707,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1702
1707
  // Note: we are not touching any batches other than mainBatch here, for two reasons:
1703
1708
  // 1. It would not help, as other batches are flushed independently from main batch.
1704
1709
  // 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
1705
- checkpoint = this.outbox.checkpoint().mainBatch;
1710
+ checkpoint = this.outbox.getBatchCheckpoints().mainBatch;
1706
1711
  }
1707
1712
  try {
1708
1713
  this._orderSequentiallyCalls++;
@@ -1793,7 +1798,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1793
1798
  return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
1794
1799
  }
1795
1800
  /**
1796
- * Are we in the middle of batching ops together?
1801
+ * Typically ops are batched and later flushed together, but in some cases we want to flush immediately.
1797
1802
  */
1798
1803
  currentlyBatching() {
1799
1804
  return this.flushMode !== FlushMode.Immediate || this._orderSequentiallyCalls !== 0;
@@ -2154,7 +2159,7 @@ export class ContainerRuntime extends TypedEventEmitter {
2154
2159
  summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
2155
2160
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
2156
2161
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
2157
- const lastAck = this.summaryCollection.latestAck;
2162
+ const lastAckedContext = this.lastAckedSummaryContext;
2158
2163
  const startSummaryResult = this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger, latestSummaryRefSeqNum);
2159
2164
  /**
2160
2165
  * This was added to validate that the summarizer node tree has the same reference sequence number from the
@@ -2206,10 +2211,10 @@ export class ContainerRuntime extends TypedEventEmitter {
2206
2211
  };
2207
2212
  }
2208
2213
  assert(summaryRefSeqNum === this.deltaManager.lastMessage?.sequenceNumber, 0x395 /* it's one and the same thing */);
2209
- if (lastAck !== this.summaryCollection.latestAck) {
2214
+ if (lastAckedContext !== this.lastAckedSummaryContext) {
2210
2215
  return {
2211
2216
  continue: false,
2212
- error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
2217
+ error: `Last summary changed while summarizing. ${this.lastAckedSummaryContext} !== ${lastAckedContext}`,
2213
2218
  };
2214
2219
  }
2215
2220
  return { continue: true };
@@ -2265,6 +2270,8 @@ export class ContainerRuntime extends TypedEventEmitter {
2265
2270
  // Counting dataStores and handles
2266
2271
  // Because handles are unchanged dataStores in the current logic,
2267
2272
  // summarized dataStore count is total dataStore count minus handle count
2273
+ // TODO why are we non null asserting here
2274
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2268
2275
  const dataStoreTree = summaryTree.tree[channelsTreeName];
2269
2276
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
2270
2277
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
@@ -2295,17 +2302,11 @@ export class ContainerRuntime extends TypedEventEmitter {
2295
2302
  error: new RetriableSummaryError(continueResult.error),
2296
2303
  };
2297
2304
  }
2298
- const summaryContext = lastAck === undefined
2299
- ? {
2300
- proposalHandle: undefined,
2301
- ackHandle: this.loadedFromVersionId,
2302
- referenceSequenceNumber: summaryRefSeqNum,
2303
- }
2304
- : {
2305
- proposalHandle: lastAck.summaryOp.contents.handle,
2306
- ackHandle: lastAck.summaryAck.contents.handle,
2307
- referenceSequenceNumber: summaryRefSeqNum,
2308
- };
2305
+ const summaryContext = {
2306
+ proposalHandle: this.lastAckedSummaryContext?.proposalHandle ?? undefined,
2307
+ ackHandle: this.lastAckedSummaryContext?.ackHandle ?? this.loadedFromVersionId,
2308
+ referenceSequenceNumber: summaryRefSeqNum,
2309
+ };
2309
2310
  let handle;
2310
2311
  try {
2311
2312
  handle = await this.storage.uploadSummaryWithContext(summarizeResult.summary, summaryContext);
@@ -2525,7 +2526,9 @@ export class ContainerRuntime extends TypedEventEmitter {
2525
2526
  else {
2526
2527
  this.outbox.submit(message);
2527
2528
  }
2528
- if (!this.currentlyBatching()) {
2529
+ // Note: Technically, the system "always" batches - if this case is true we'll just have a single-message batch.
2530
+ const flushImmediatelyOnSubmit = !this.currentlyBatching();
2531
+ if (flushImmediatelyOnSubmit) {
2529
2532
  this.flush();
2530
2533
  }
2531
2534
  else {
@@ -2592,13 +2595,16 @@ export class ContainerRuntime extends TypedEventEmitter {
2592
2595
  throw new Error("Runtime is closed");
2593
2596
  }
2594
2597
  }
2595
- reSubmitBatch(batch) {
2598
+ reSubmitBatch(batch, batchId) {
2596
2599
  this.orderSequentially(() => {
2597
2600
  for (const message of batch) {
2598
2601
  this.reSubmit(message);
2599
2602
  }
2600
2603
  });
2601
- this.flush();
2604
+ // Only include Batch ID if "Offline Load" feature is enabled
2605
+ // It's only needed to identify batches across container forks arising from misuse of offline load.
2606
+ const includeBatchId = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
2607
+ this.flush(includeBatchId ? batchId : undefined);
2602
2608
  }
2603
2609
  reSubmit(message) {
2604
2610
  // Need to parse from string for back-compat
@@ -2689,45 +2695,91 @@ export class ContainerRuntime extends TypedEventEmitter {
2689
2695
  const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
2690
2696
  // proposalHandle is always passed from RunningSummarizer.
2691
2697
  assert(proposalHandle !== undefined, 0x766 /* proposalHandle should be available */);
2692
- const readAndParseBlob = async (id) => readAndParse(this.storage, id);
2693
2698
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq);
2699
+ /* eslint-disable jsdoc/check-indentation */
2694
2700
  /**
2695
- * When refreshing a summary ack, this check indicates a new ack of a summary that is newer than the
2696
- * current summary that is tracked, but this summarizer runtime did not produce/track that summary. Thus
2697
- * it needs to refresh its state. Today refresh is done by fetching the latest snapshot to update the cache
2698
- * and then close as the current main client is likely to be re-elected as the parent summarizer again.
2701
+ * If the snapshot corresponding to the ack is not tracked by this client, it was submitted by another client.
2702
+ * Take action as per the following scenarios:
2703
+ * 1. If that snapshot is older than the one tracked by this client, ignore the ack because only the latest
2704
+ * snapshot is tracked.
2705
+ * 2. If that snapshot is newer, attempt to fetch the latest snapshot and do one of the following:
2706
+ * 2.1. If the fetched snapshot is same or newer than the one for which ack was received, close this client.
2707
+ * The next summarizer client will likely start from this snapshot and get out of this state. Fetching
2708
+ * the snapshot updates the cache for this client so if it's re-elected as summarizer, this will prevent
2709
+ * any thrashing.
2710
+ * 2.2. If the fetched snapshot is older than the one for which ack was received, ignore the ack. This can
2711
+ * happen in scenarios where the snapshot for the ack was lost in storage (in scenarios like DB rollback,
2712
+ * etc.) but the summary ack is still there because it's tracked a different service. In such cases,
2713
+ * ignoring the ack is the correct thing to do because the latest snapshot in storage is not the one for
2714
+ * the ack but is still the one tracked by this client. If we were to close the summarizer like in the
2715
+ * previous scenario, it will result in this document stuck in this state in a loop.
2699
2716
  */
2700
- if (!result.isSummaryTracked && result.isSummaryNewer) {
2701
- await this.fetchLatestSnapshotAndClose(summaryLogger, {
2702
- eventName: "RefreshLatestSummaryAckFetch",
2703
- ackHandle,
2704
- targetSequenceNumber: summaryRefSeq,
2705
- }, readAndParseBlob);
2717
+ /* eslint-enable jsdoc/check-indentation */
2718
+ if (!result.isSummaryTracked) {
2719
+ if (result.isSummaryNewer) {
2720
+ await this.fetchLatestSnapshotAndMaybeClose(summaryRefSeq, ackHandle, summaryLogger);
2721
+ }
2706
2722
  return;
2707
2723
  }
2708
2724
  // Notify the garbage collector so it can update its latest summary state.
2709
2725
  await this.garbageCollector.refreshLatestSummary(result);
2726
+ // If we here, the ack was tracked by this client. Update the summary context of the last ack.
2727
+ this.lastAckedSummaryContext = {
2728
+ proposalHandle,
2729
+ ackHandle,
2730
+ referenceSequenceNumber: summaryRefSeq,
2731
+ };
2710
2732
  }
2711
2733
  /**
2712
- * Fetches the latest snapshot from storage and closes the container. This is done in cases where
2713
- * the last known snapshot is older than the latest one. This will ensure that the latest snapshot
2714
- * is downloaded and we don't end up loading snapshot from cache.
2734
+ * Fetches the latest snapshot from storage. If the fetched snapshot is same or newer than the one for which ack
2735
+ * was received, close this client. Fetching the snapshot will update the cache for this client so if it's
2736
+ * re-elected as summarizer, this will prevent any thrashing.
2737
+ * If the fetched snapshot is older than the one for which ack was received, ignore the ack and return. This can
2738
+ * happen in scenarios where the snapshot for the ack was lost in storage in scenarios like DB rollback, etc.
2715
2739
  */
2716
- async fetchLatestSnapshotAndClose(logger, event, readAndParseBlob) {
2717
- await PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
2718
- const stats = {};
2740
+ async fetchLatestSnapshotAndMaybeClose(targetRefSeq, targetAckHandle, logger) {
2741
+ const fetchedSnapshotRefSeq = await PerformanceEvent.timedExecAsync(logger, { eventName: "RefreshLatestSummaryAckFetch" }, async (perfEvent) => {
2742
+ const props = { targetRefSeq, targetAckHandle };
2719
2743
  const trace = Trace.start();
2720
- const versions = await this.storage.getVersions(null, 1, "prefetchLatestSummaryBeforeClose", FetchSource.noCache);
2721
- assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2722
- stats.getVersionDuration = trace.trace().duration;
2723
- const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
2724
- assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
2725
- stats.getSnapshotDuration = trace.trace().duration;
2726
- const latestSnapshotRefSeq = await seqFromTree(maybeSnapshot, readAndParseBlob);
2727
- stats.snapshotRefSeq = latestSnapshotRefSeq;
2728
- stats.snapshotVersion = versions[0].id;
2729
- perfEvent.end(stats);
2744
+ let snapshotTree;
2745
+ const scenarioName = "RefreshLatestSummaryAckFetch";
2746
+ // If loader supplied us the ISnapshot when loading, the new getSnapshotApi is supported and feature gate is ON, then use the
2747
+ // new API, otherwise it will reduce the service performance because the service will need to recalculate the full snapshot
2748
+ // in case previously getSnapshotApi was used and now we use the getVersions API.
2749
+ if (this.isSnapshotInstanceOfISnapshot &&
2750
+ this.storage.getSnapshot !== undefined &&
2751
+ this.mc.config.getBoolean("Fluid.Container.UseLoadingGroupIdForSnapshotFetch2") ===
2752
+ true) {
2753
+ const snapshot = await this.storage.getSnapshot({
2754
+ scenarioName,
2755
+ fetchSource: FetchSource.noCache,
2756
+ });
2757
+ const id = snapshot.snapshotTree.id;
2758
+ assert(id !== undefined, 0x9d0 /* id of the fetched snapshot should be defined */);
2759
+ props.snapshotVersion = id;
2760
+ snapshotTree = snapshot.snapshotTree;
2761
+ }
2762
+ else {
2763
+ const versions = await this.storage.getVersions(null, 1, scenarioName, FetchSource.noCache);
2764
+ assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2765
+ snapshotTree = await this.storage.getSnapshotTree(versions[0]);
2766
+ assert(!!snapshotTree, 0x138 /* "Failed to get snapshot from storage" */);
2767
+ props.snapshotVersion = versions[0].id;
2768
+ }
2769
+ props.getSnapshotDuration = trace.trace().duration;
2770
+ const readAndParseBlob = async (id) => readAndParse(this.storage, id);
2771
+ const snapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
2772
+ props.snapshotRefSeq = snapshotRefSeq;
2773
+ props.newerSnapshotPresent = snapshotRefSeq >= targetRefSeq;
2774
+ perfEvent.end({ details: props });
2775
+ return snapshotRefSeq;
2730
2776
  });
2777
+ // If the snapshot that was fetched is older than the target snapshot, return. The summarizer will not be closed
2778
+ // because the snapshot is likely deleted from storage and it so, closing the summarizer will result in the
2779
+ // document being stuck in this state.
2780
+ if (fetchedSnapshotRefSeq < targetRefSeq) {
2781
+ return;
2782
+ }
2731
2783
  await delay(this.closeSummarizerDelayMs);
2732
2784
  this._summarizer?.stop("latestSummaryStateStale");
2733
2785
  this.disposeFn();