@fluidframework/container-runtime 2.1.0 → 2.3.0-288113

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 (198) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +2 -2
  3. package/api-report/container-runtime.legacy.alpha.api.md +4 -3
  4. package/container-runtime.test-files.tar +0 -0
  5. package/dist/batchTracker.d.ts.map +1 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager/blobManager.d.ts.map +1 -1
  8. package/dist/blobManager/blobManager.js +9 -0
  9. package/dist/blobManager/blobManager.js.map +1 -1
  10. package/dist/channelCollection.d.ts +0 -14
  11. package/dist/channelCollection.d.ts.map +1 -1
  12. package/dist/channelCollection.js +2 -12
  13. package/dist/channelCollection.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +34 -6
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +176 -81
  17. package/dist/containerRuntime.js.map +1 -1
  18. package/dist/dataStoreContext.d.ts +9 -18
  19. package/dist/dataStoreContext.d.ts.map +1 -1
  20. package/dist/dataStoreContext.js +40 -78
  21. package/dist/dataStoreContext.js.map +1 -1
  22. package/dist/gc/garbageCollection.d.ts +0 -6
  23. package/dist/gc/garbageCollection.d.ts.map +1 -1
  24. package/dist/gc/garbageCollection.js +23 -66
  25. package/dist/gc/garbageCollection.js.map +1 -1
  26. package/dist/gc/gcConfigs.d.ts.map +1 -1
  27. package/dist/gc/gcConfigs.js +11 -34
  28. package/dist/gc/gcConfigs.js.map +1 -1
  29. package/dist/gc/gcDefinitions.d.ts +9 -52
  30. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  31. package/dist/gc/gcDefinitions.js +3 -23
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/gc/gcHelpers.d.ts.map +1 -1
  34. package/dist/gc/gcHelpers.js +2 -6
  35. package/dist/gc/gcHelpers.js.map +1 -1
  36. package/dist/gc/gcSummaryStateTracker.d.ts +1 -1
  37. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  38. package/dist/gc/gcSummaryStateTracker.js +4 -8
  39. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  40. package/dist/gc/gcTelemetry.d.ts +1 -9
  41. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  42. package/dist/gc/gcTelemetry.js +3 -25
  43. package/dist/gc/gcTelemetry.js.map +1 -1
  44. package/dist/gc/index.d.ts +2 -2
  45. package/dist/gc/index.d.ts.map +1 -1
  46. package/dist/gc/index.js +2 -7
  47. package/dist/gc/index.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/messageTypes.d.ts +6 -5
  53. package/dist/messageTypes.d.ts.map +1 -1
  54. package/dist/messageTypes.js.map +1 -1
  55. package/dist/metadata.d.ts +9 -1
  56. package/dist/metadata.d.ts.map +1 -1
  57. package/dist/metadata.js +6 -1
  58. package/dist/metadata.js.map +1 -1
  59. package/dist/opLifecycle/index.d.ts +1 -1
  60. package/dist/opLifecycle/index.d.ts.map +1 -1
  61. package/dist/opLifecycle/index.js.map +1 -1
  62. package/dist/opLifecycle/opGroupingManager.d.ts +8 -0
  63. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  64. package/dist/opLifecycle/opGroupingManager.js +34 -2
  65. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  66. package/dist/opLifecycle/outbox.d.ts +1 -0
  67. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  68. package/dist/opLifecycle/outbox.js +20 -0
  69. package/dist/opLifecycle/outbox.js.map +1 -1
  70. package/dist/opLifecycle/remoteMessageProcessor.d.ts +34 -15
  71. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  72. package/dist/opLifecycle/remoteMessageProcessor.js +59 -46
  73. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  74. package/dist/packageVersion.d.ts +1 -1
  75. package/dist/packageVersion.d.ts.map +1 -1
  76. package/dist/packageVersion.js +1 -1
  77. package/dist/packageVersion.js.map +1 -1
  78. package/dist/pendingStateManager.d.ts +28 -27
  79. package/dist/pendingStateManager.d.ts.map +1 -1
  80. package/dist/pendingStateManager.js +143 -112
  81. package/dist/pendingStateManager.js.map +1 -1
  82. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  83. package/dist/summary/summarizerNode/summarizerNode.js +5 -1
  84. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  85. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
  86. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  87. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
  88. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  89. package/lib/batchTracker.d.ts.map +1 -1
  90. package/lib/batchTracker.js.map +1 -1
  91. package/lib/blobManager/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager/blobManager.js +9 -0
  93. package/lib/blobManager/blobManager.js.map +1 -1
  94. package/lib/channelCollection.d.ts +0 -14
  95. package/lib/channelCollection.d.ts.map +1 -1
  96. package/lib/channelCollection.js +2 -11
  97. package/lib/channelCollection.js.map +1 -1
  98. package/lib/containerRuntime.d.ts +34 -6
  99. package/lib/containerRuntime.d.ts.map +1 -1
  100. package/lib/containerRuntime.js +176 -81
  101. package/lib/containerRuntime.js.map +1 -1
  102. package/lib/dataStoreContext.d.ts +9 -18
  103. package/lib/dataStoreContext.d.ts.map +1 -1
  104. package/lib/dataStoreContext.js +27 -65
  105. package/lib/dataStoreContext.js.map +1 -1
  106. package/lib/gc/garbageCollection.d.ts +0 -6
  107. package/lib/gc/garbageCollection.d.ts.map +1 -1
  108. package/lib/gc/garbageCollection.js +25 -68
  109. package/lib/gc/garbageCollection.js.map +1 -1
  110. package/lib/gc/gcConfigs.d.ts.map +1 -1
  111. package/lib/gc/gcConfigs.js +12 -35
  112. package/lib/gc/gcConfigs.js.map +1 -1
  113. package/lib/gc/gcDefinitions.d.ts +9 -52
  114. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  115. package/lib/gc/gcDefinitions.js +2 -22
  116. package/lib/gc/gcDefinitions.js.map +1 -1
  117. package/lib/gc/gcHelpers.d.ts.map +1 -1
  118. package/lib/gc/gcHelpers.js +2 -6
  119. package/lib/gc/gcHelpers.js.map +1 -1
  120. package/lib/gc/gcSummaryStateTracker.d.ts +1 -1
  121. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  122. package/lib/gc/gcSummaryStateTracker.js +4 -8
  123. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  124. package/lib/gc/gcTelemetry.d.ts +1 -9
  125. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  126. package/lib/gc/gcTelemetry.js +3 -24
  127. package/lib/gc/gcTelemetry.js.map +1 -1
  128. package/lib/gc/index.d.ts +2 -2
  129. package/lib/gc/index.d.ts.map +1 -1
  130. package/lib/gc/index.js +2 -2
  131. package/lib/gc/index.js.map +1 -1
  132. package/lib/index.d.ts +1 -1
  133. package/lib/index.d.ts.map +1 -1
  134. package/lib/index.js +1 -1
  135. package/lib/index.js.map +1 -1
  136. package/lib/messageTypes.d.ts +6 -5
  137. package/lib/messageTypes.d.ts.map +1 -1
  138. package/lib/messageTypes.js.map +1 -1
  139. package/lib/metadata.d.ts +9 -1
  140. package/lib/metadata.d.ts.map +1 -1
  141. package/lib/metadata.js +4 -0
  142. package/lib/metadata.js.map +1 -1
  143. package/lib/opLifecycle/index.d.ts +1 -1
  144. package/lib/opLifecycle/index.d.ts.map +1 -1
  145. package/lib/opLifecycle/index.js.map +1 -1
  146. package/lib/opLifecycle/opGroupingManager.d.ts +8 -0
  147. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  148. package/lib/opLifecycle/opGroupingManager.js +34 -2
  149. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  150. package/lib/opLifecycle/outbox.d.ts +1 -0
  151. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  152. package/lib/opLifecycle/outbox.js +20 -0
  153. package/lib/opLifecycle/outbox.js.map +1 -1
  154. package/lib/opLifecycle/remoteMessageProcessor.d.ts +34 -15
  155. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  156. package/lib/opLifecycle/remoteMessageProcessor.js +59 -46
  157. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  158. package/lib/packageVersion.d.ts +1 -1
  159. package/lib/packageVersion.d.ts.map +1 -1
  160. package/lib/packageVersion.js +1 -1
  161. package/lib/packageVersion.js.map +1 -1
  162. package/lib/pendingStateManager.d.ts +28 -27
  163. package/lib/pendingStateManager.d.ts.map +1 -1
  164. package/lib/pendingStateManager.js +144 -113
  165. package/lib/pendingStateManager.js.map +1 -1
  166. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  167. package/lib/summary/summarizerNode/summarizerNode.js +5 -1
  168. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  169. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
  170. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  171. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
  172. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  173. package/package.json +22 -35
  174. package/src/batchTracker.ts +4 -2
  175. package/src/blobManager/blobManager.ts +9 -0
  176. package/src/channelCollection.ts +2 -11
  177. package/src/containerRuntime.ts +211 -109
  178. package/src/dataStoreContext.ts +29 -93
  179. package/src/gc/garbageCollection.ts +26 -79
  180. package/src/gc/gcConfigs.ts +12 -45
  181. package/src/gc/gcDefinitions.ts +10 -55
  182. package/src/gc/gcHelpers.ts +10 -8
  183. package/src/gc/gcSummaryStateTracker.ts +6 -9
  184. package/src/gc/gcTelemetry.ts +3 -38
  185. package/src/gc/index.ts +2 -6
  186. package/src/index.ts +0 -1
  187. package/src/messageTypes.ts +12 -11
  188. package/src/metadata.ts +16 -2
  189. package/src/opLifecycle/index.ts +1 -0
  190. package/src/opLifecycle/opGroupingManager.ts +42 -3
  191. package/src/opLifecycle/outbox.ts +30 -0
  192. package/src/opLifecycle/remoteMessageProcessor.ts +98 -62
  193. package/src/packageVersion.ts +1 -1
  194. package/src/pendingStateManager.ts +199 -177
  195. package/src/summary/README.md +31 -28
  196. package/src/summary/summarizerNode/summarizerNode.ts +6 -1
  197. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +20 -43
  198. package/src/summary/summaryFormats.md +25 -22
@@ -162,7 +162,7 @@ import {
162
162
  ContainerRuntimeGCMessage,
163
163
  type ContainerRuntimeIdAllocationMessage,
164
164
  type InboundSequencedContainerRuntimeMessage,
165
- type InboundSequencedContainerRuntimeMessageOrSystemMessage,
165
+ type InboundSequencedNonContainerRuntimeMessage,
166
166
  type LocalContainerRuntimeMessage,
167
167
  type OutboundContainerRuntimeMessage,
168
168
  type UnknownContainerRuntimeMessage,
@@ -180,6 +180,7 @@ import {
180
180
  OpSplitter,
181
181
  Outbox,
182
182
  RemoteMessageProcessor,
183
+ type InboundBatch,
183
184
  } from "./opLifecycle/index.js";
184
185
  import { pkgVersion } from "./packageVersion.js";
185
186
  import {
@@ -522,6 +523,9 @@ export const TombstoneResponseHeaderKey = "isTombstoned";
522
523
  * Inactive error responses will have this header set to true
523
524
  * @legacy
524
525
  * @alpha
526
+ *
527
+ * @deprecated this header is deprecated and will be removed in the future. The functionality corresponding
528
+ * to this was experimental and is no longer supported.
525
529
  */
526
530
  export const InactiveResponseHeaderKey = "isInactive";
527
531
 
@@ -533,7 +537,6 @@ export interface RuntimeHeaderData {
533
537
  wait?: boolean;
534
538
  viaHandle?: boolean;
535
539
  allowTombstone?: boolean;
536
- allowInactive?: boolean;
537
540
  }
538
541
 
539
542
  /** Default values for Runtime Headers */
@@ -541,7 +544,6 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
541
544
  wait: true,
542
545
  viaHandle: false,
543
546
  allowTombstone: false,
544
- allowInactive: false,
545
547
  };
546
548
 
547
549
  /**
@@ -629,6 +631,7 @@ export const defaultPendingOpsRetryDelayMs = 1000;
629
631
  const defaultCloseSummarizerDelayMs = 5000; // 5 seconds
630
632
 
631
633
  /**
634
+ * Checks whether a message.type is one of the values in ContainerMessageType
632
635
  * @deprecated please use version in driver-utils
633
636
  * @internal
634
637
  */
@@ -688,9 +691,7 @@ export const makeLegacySendBatchFn =
688
691
  /** Helper type for type constraints passed through several functions.
689
692
  * local - Did this client send the op?
690
693
  * savedOp - Is this op being replayed after being serialized (having been sequenced previously)
691
- * batchStartCsn - The clientSequenceNumber given on submit to the start of this batch
692
- * message - The unpacked message. Likely a TypedContainerRuntimeMessage, but could also be a system op
693
- * modernRuntimeMessage - Does this appear like a current TypedContainerRuntimeMessage?
694
+ * localOpMetadata - Metadata maintained locally for a local op.
694
695
  */
695
696
  type MessageWithContext = {
696
697
  local: boolean;
@@ -699,14 +700,13 @@ type MessageWithContext = {
699
700
  } & (
700
701
  | {
701
702
  message: InboundSequencedContainerRuntimeMessage;
702
- modernRuntimeMessage: true;
703
+ isRuntimeMessage: true;
703
704
  }
704
705
  | {
705
- message: InboundSequencedContainerRuntimeMessageOrSystemMessage;
706
- modernRuntimeMessage: false;
706
+ message: InboundSequencedNonContainerRuntimeMessage;
707
+ isRuntimeMessage: false;
707
708
  }
708
709
  );
709
-
710
710
  const summarizerRequestUrl = "_summarizer";
711
711
 
712
712
  /**
@@ -761,6 +761,23 @@ function lastMessageFromMetadata(metadata: IContainerRuntimeMetadata | undefined
761
761
  : metadata?.message;
762
762
  }
763
763
 
764
+ /**
765
+ * There is some ancient back-compat code that we'd like to instrument
766
+ * to understand if/when it is hit.
767
+ * We only want to log this once, to avoid spamming telemetry if we are wrong and these cases are hit commonly.
768
+ */
769
+ let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: string) => {
770
+ // We only want to log this once per ContainerRuntime instance, to avoid spamming telemetry.
771
+ getSingleUseLegacyLogCallback = () => () => {};
772
+
773
+ return (codePath: string) => {
774
+ logger.sendTelemetryEvent({
775
+ eventName: "LegacyMessageFormat",
776
+ details: { codePath, type },
777
+ });
778
+ };
779
+ };
780
+
764
781
  /**
765
782
  * Represents the runtime of the container. Contains helper functions/state of the container.
766
783
  * It will define the store level mappings.
@@ -1219,6 +1236,7 @@ export class ContainerRuntime
1219
1236
 
1220
1237
  private _orderSequentiallyCalls: number = 0;
1221
1238
  private readonly _flushMode: FlushMode;
1239
+ private readonly offlineEnabled: boolean;
1222
1240
  private flushTaskExists = false;
1223
1241
 
1224
1242
  private _connected: boolean;
@@ -1330,14 +1348,22 @@ export class ContainerRuntime
1330
1348
  */
1331
1349
  private nextSummaryNumber: number;
1332
1350
 
1333
- /** If false, loading or using a Tombstoned object should merely log, not fail */
1351
+ /**
1352
+ * If false, loading or using a Tombstoned object should merely log, not fail.
1353
+ * @deprecated NOT SUPPORTED - hardcoded to return false since it's deprecated.
1354
+ */
1355
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
1334
1356
  public get gcTombstoneEnforcementAllowed(): boolean {
1335
- return this.garbageCollector.tombstoneEnforcementAllowed;
1357
+ return false;
1336
1358
  }
1337
1359
 
1338
- /** If true, throw an error when a tombstone data store is used. */
1360
+ /**
1361
+ * If true, throw an error when a tombstone data store is used.
1362
+ * @deprecated NOT SUPPORTED - hardcoded to return false since it's deprecated.
1363
+ */
1364
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
1339
1365
  public get gcThrowOnTombstoneUsage(): boolean {
1340
- return this.garbageCollector.throwOnTombstoneUsage;
1366
+ return false;
1341
1367
  }
1342
1368
 
1343
1369
  /**
@@ -1547,7 +1573,6 @@ export class ContainerRuntime
1547
1573
  {
1548
1574
  applyStashedOp: this.applyStashedOp.bind(this),
1549
1575
  clientId: () => this.clientId,
1550
- close: this.closeFn,
1551
1576
  connected: () => this.connected,
1552
1577
  reSubmitBatch: this.reSubmitBatch.bind(this),
1553
1578
  isActiveConnection: () => this.innerDeltaManager.active,
@@ -1602,6 +1627,14 @@ export class ContainerRuntime
1602
1627
  } else {
1603
1628
  this._flushMode = runtimeOptions.flushMode;
1604
1629
  }
1630
+ this.offlineEnabled =
1631
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
1632
+
1633
+ if (this.offlineEnabled && this._flushMode !== FlushMode.TurnBased) {
1634
+ const error = new UsageError("Offline mode is only supported in turn-based mode");
1635
+ this.closeFn(error);
1636
+ throw error;
1637
+ }
1605
1638
 
1606
1639
  if (context.attachState === AttachState.Attached) {
1607
1640
  const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
@@ -2609,73 +2642,92 @@ export class ContainerRuntime
2609
2642
  await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
2610
2643
  }
2611
2644
 
2612
- public process(messageArg: ISequencedDocumentMessage, local: boolean) {
2645
+ /**
2646
+ * Processes the op.
2647
+ * @param messageCopy - Sequenced message for a distributed document.
2648
+ * @param local - true if the message was originally generated by the client receiving it.
2649
+ */
2650
+ public process({ ...messageCopy }: ISequencedDocumentMessage, local: boolean) {
2651
+ // spread operator above ensure we make a shallow copy of message, as the processing flow will modify it.
2652
+ // There might be multiple container instances receiving the same message.
2653
+
2613
2654
  this.verifyNotClosed();
2614
2655
 
2615
2656
  // Whether or not the message appears to be a runtime message from an up-to-date client.
2616
2657
  // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
2617
2658
  // or something different, like a system message.
2618
- const modernRuntimeMessage = messageArg.type === MessageType.Operation;
2619
-
2620
- const savedOp = (messageArg.metadata as ISavedOpMetadata)?.savedOp;
2659
+ const hasModernRuntimeMessageEnvelope = messageCopy.type === MessageType.Operation;
2660
+ const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2661
+ const logLegacyCase = getSingleUseLegacyLogCallback(this.logger, messageCopy.type);
2621
2662
 
2622
- // There is some ancient back-compat code that we'd like to instrument
2623
- // to understand if/when it is hit.
2624
- const logLegacyCase = (codePath: string) =>
2625
- this.logger.sendTelemetryEvent({
2626
- eventName: "LegacyMessageFormat",
2627
- details: { codePath, type: messageArg.type },
2628
- });
2629
-
2630
- // Do shallow copy of message, as the processing flow will modify it.
2631
- // There might be multiple container instances receiving the same message.
2632
- // We do not need to make a deep copy. Each layer will just replace message.contents itself,
2633
- // but will not modify the contents object (likely it will replace it on the message).
2634
- const messageCopy = { ...messageArg };
2635
2663
  // We expect runtime messages to have JSON contents - deserialize it in place.
2636
- ensureContentsDeserialized(messageCopy, modernRuntimeMessage, logLegacyCase);
2637
- if (modernRuntimeMessage) {
2638
- const processResult = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
2639
- if (processResult === undefined) {
2664
+ ensureContentsDeserialized(messageCopy, hasModernRuntimeMessageEnvelope, logLegacyCase);
2665
+ if (hasModernRuntimeMessageEnvelope) {
2666
+ // If the message has the modern message envelope, then process it here.
2667
+ // Here we unpack the message (decompress, unchunk, and/or ungroup) into a batch of messages with ContainerMessageType
2668
+ const inboundBatch = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
2669
+ if (inboundBatch === undefined) {
2640
2670
  // This means the incoming message is an incomplete part of a message or batch
2641
2671
  // and we need to process more messages before the rest of the system can understand it.
2642
2672
  return;
2643
2673
  }
2644
- const batchStartCsn = processResult.batchStartCsn;
2645
- const batch = processResult.messages;
2646
- const messages: {
2647
- message: InboundSequencedContainerRuntimeMessage;
2648
- localOpMetadata: unknown;
2649
- }[] = local
2650
- ? this.pendingStateManager.processPendingLocalBatch(batch, batchStartCsn)
2651
- : batch.map((message) => ({ message, localOpMetadata: undefined }));
2652
- messages.forEach(({ message, localOpMetadata }) => {
2653
- const msg: MessageWithContext = {
2654
- message,
2655
- local,
2656
- modernRuntimeMessage,
2657
- savedOp,
2658
- localOpMetadata,
2659
- };
2660
- this.ensureNoDataModelChanges(() => this.processCore(msg));
2661
- });
2662
- } else {
2663
- const msg: MessageWithContext = {
2664
- message: messageCopy as InboundSequencedContainerRuntimeMessageOrSystemMessage,
2674
+
2675
+ // Reach out to PendingStateManager to zip localOpMetadata into the message list if it's a local batch
2676
+ const messagesWithPendingState = this.pendingStateManager.processInboundBatch(
2677
+ inboundBatch,
2665
2678
  local,
2666
- modernRuntimeMessage,
2667
- savedOp,
2668
- };
2669
- this.ensureNoDataModelChanges(() => this.processCore(msg));
2679
+ );
2680
+ if (messagesWithPendingState.length > 0) {
2681
+ messagesWithPendingState.forEach(({ message, localOpMetadata }) => {
2682
+ const msg: MessageWithContext = {
2683
+ message,
2684
+ local,
2685
+ isRuntimeMessage: true,
2686
+ savedOp,
2687
+ localOpMetadata,
2688
+ };
2689
+ this.ensureNoDataModelChanges(() => this.processRuntimeMessage(msg));
2690
+ });
2691
+ } else {
2692
+ this.ensureNoDataModelChanges(() => this.processEmptyBatch(inboundBatch, local));
2693
+ }
2694
+ } else {
2695
+ // Check if message.type is one of values in ContainerMessageType
2696
+ // eslint-disable-next-line import/no-deprecated
2697
+ if (isRuntimeMessage(messageCopy)) {
2698
+ // Legacy op received
2699
+ this.ensureNoDataModelChanges(() =>
2700
+ this.processRuntimeMessage({
2701
+ message: messageCopy as InboundSequencedContainerRuntimeMessage,
2702
+ local,
2703
+ isRuntimeMessage: true,
2704
+ savedOp,
2705
+ }),
2706
+ );
2707
+ } else {
2708
+ // A non container runtime message (like other system ops - join, ack, leave, nack etc.)
2709
+ this.ensureNoDataModelChanges(() =>
2710
+ this.observeNonRuntimeMessage({
2711
+ message: messageCopy as InboundSequencedNonContainerRuntimeMessage,
2712
+ local,
2713
+ isRuntimeMessage: false,
2714
+ savedOp,
2715
+ }),
2716
+ );
2717
+ }
2670
2718
  }
2671
2719
  }
2672
2720
 
2673
2721
  private _processedClientSequenceNumber: number | undefined;
2674
2722
 
2675
2723
  /**
2676
- * Direct the message to the correct subsystem for processing, and implement other side effects
2724
+ * Processes messages that are intended for the runtime layer to process.
2725
+ * It redirects the message to the correct subsystem for processing, and implement other side effects
2726
+ * @param messageWithContext - message to process with additional context and isRuntimeMessage prop as true
2677
2727
  */
2678
- private processCore(messageWithContext: MessageWithContext) {
2728
+ private processRuntimeMessage(
2729
+ messageWithContext: MessageWithContext & { isRuntimeMessage: true },
2730
+ ) {
2679
2731
  const { message, local, localOpMetadata } = messageWithContext;
2680
2732
 
2681
2733
  // Intercept to reduce minimum sequence number to the delta manager's minimum sequence number.
@@ -2710,7 +2762,75 @@ export class ContainerRuntime
2710
2762
 
2711
2763
  this.validateAndProcessRuntimeMessage(messageWithContext, localOpMetadata);
2712
2764
 
2713
- this.emit("op", message, messageWithContext.modernRuntimeMessage);
2765
+ this.emit("op", message, messageWithContext.isRuntimeMessage);
2766
+
2767
+ this.scheduleManager.afterOpProcessing(undefined, message);
2768
+
2769
+ if (local) {
2770
+ // If we have processed a local op, this means that the container is
2771
+ // making progress and we can reset the counter for how many times
2772
+ // we have consecutively replayed the pending states
2773
+ this.resetReconnectCount();
2774
+ }
2775
+ } catch (e) {
2776
+ this.scheduleManager.afterOpProcessing(e, message);
2777
+ throw e;
2778
+ }
2779
+ }
2780
+
2781
+ /**
2782
+ * Process an empty batch, which will execute expected actions while processing even if there are no messages.
2783
+ * This is a separate function because the processCore function expects at least one message to process.
2784
+ * It is expected to happen only when the outbox produces an empty batch due to a resubmit flow.
2785
+ */
2786
+ private processEmptyBatch(emptyBatch: InboundBatch, local: boolean) {
2787
+ const { emptyBatchSequenceNumber: sequenceNumber, batchStartCsn } = emptyBatch;
2788
+ assert(sequenceNumber !== undefined, 0x9fa /* emptyBatchSequenceNumber must be defined */);
2789
+ this.emit("batchBegin", { sequenceNumber });
2790
+ this._processedClientSequenceNumber = batchStartCsn;
2791
+ if (!this.hasPendingMessages()) {
2792
+ this.updateDocumentDirtyState(false);
2793
+ }
2794
+ this.emit("batchEnd", undefined, { sequenceNumber });
2795
+ if (local) {
2796
+ this.resetReconnectCount();
2797
+ }
2798
+ }
2799
+
2800
+ /**
2801
+ * Observes messages that are not intended for the runtime layer, updating/notifying Runtime systems as needed.
2802
+ * @param messageWithContext - non-runtime messages to process with additional context and isRuntimeMessage prop as false
2803
+ */
2804
+ private observeNonRuntimeMessage(
2805
+ messageWithContext: MessageWithContext & { isRuntimeMessage: false },
2806
+ ) {
2807
+ const { message, local } = messageWithContext;
2808
+
2809
+ // Intercept to reduce minimum sequence number to the delta manager's minimum sequence number.
2810
+ // Sequence numbers are not guaranteed to follow any sort of order. Re-entrancy is one of those situations
2811
+ if (
2812
+ this.deltaManager.minimumSequenceNumber <
2813
+ messageWithContext.message.minimumSequenceNumber
2814
+ ) {
2815
+ messageWithContext.message.minimumSequenceNumber =
2816
+ this.deltaManager.minimumSequenceNumber;
2817
+ }
2818
+
2819
+ // Surround the actual processing of the operation with messages to the schedule manager indicating
2820
+ // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
2821
+ // messages once a batch has been fully processed.
2822
+ this.scheduleManager.beforeOpProcessing(message);
2823
+
2824
+ this._processedClientSequenceNumber = message.clientSequenceNumber;
2825
+
2826
+ try {
2827
+ // If there are no more pending messages after processing a local message,
2828
+ // the document is no longer dirty.
2829
+ if (!this.hasPendingMessages()) {
2830
+ this.updateDocumentDirtyState(false);
2831
+ }
2832
+
2833
+ this.emit("op", message, messageWithContext.isRuntimeMessage);
2714
2834
 
2715
2835
  this.scheduleManager.afterOpProcessing(undefined, message);
2716
2836
 
@@ -2725,33 +2845,33 @@ export class ContainerRuntime
2725
2845
  throw e;
2726
2846
  }
2727
2847
  }
2848
+
2728
2849
  /**
2729
2850
  * Assuming the given message is also a TypedContainerRuntimeMessage,
2730
2851
  * checks its type and dispatches the message to the appropriate handler in the runtime.
2731
2852
  * Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
2732
2853
  */
2733
2854
  private validateAndProcessRuntimeMessage(
2734
- messageWithContext: MessageWithContext,
2855
+ messageWithContext: MessageWithContext & { isRuntimeMessage: true },
2735
2856
  localOpMetadata: unknown,
2736
2857
  ): void {
2737
- // TODO: destructure message and modernRuntimeMessage once using typescript 5.2.2+
2738
- const { local } = messageWithContext;
2739
- switch (messageWithContext.message.type) {
2858
+ const { local, message, savedOp } = messageWithContext;
2859
+ switch (message.type) {
2740
2860
  case ContainerMessageType.Attach:
2741
2861
  case ContainerMessageType.Alias:
2742
2862
  case ContainerMessageType.FluidDataStoreOp:
2743
- this.channelCollection.process(messageWithContext.message, local, localOpMetadata);
2863
+ this.channelCollection.process(message, local, localOpMetadata);
2744
2864
  break;
2745
2865
  case ContainerMessageType.BlobAttach:
2746
- this.blobManager.processBlobAttachOp(messageWithContext.message, local);
2866
+ this.blobManager.processBlobAttachOp(message, local);
2747
2867
  break;
2748
2868
  case ContainerMessageType.IdAllocation:
2749
2869
  // Don't re-finalize the range if we're processing a "savedOp" in
2750
2870
  // stashed ops flow. The compressor is stashed with these ops already processed.
2751
2871
  // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
2752
2872
  // thus we need to process all the ops.
2753
- if (!(this.skipSavedCompressorOps && messageWithContext.savedOp === true)) {
2754
- const range = messageWithContext.message.contents;
2873
+ if (!(this.skipSavedCompressorOps && savedOp === true)) {
2874
+ const range = message.contents;
2755
2875
  // Some other client turned on the id compressor. If we have not turned it on,
2756
2876
  // put it in a pending queue and delay finalization.
2757
2877
  if (this._idCompressor === undefined) {
@@ -2770,11 +2890,7 @@ export class ContainerRuntime
2770
2890
  }
2771
2891
  break;
2772
2892
  case ContainerMessageType.GC:
2773
- this.garbageCollector.processMessage(
2774
- messageWithContext.message,
2775
- messageWithContext.message.timestamp,
2776
- local,
2777
- );
2893
+ this.garbageCollector.processMessage(message, message.timestamp, local);
2778
2894
  break;
2779
2895
  case ContainerMessageType.ChunkedOp:
2780
2896
  // From observability POV, we should not exppse the rest of the system (including "op" events on object) to these messages.
@@ -2784,23 +2900,14 @@ export class ContainerRuntime
2784
2900
  break;
2785
2901
  case ContainerMessageType.DocumentSchemaChange:
2786
2902
  this.documentsSchemaController.processDocumentSchemaOp(
2787
- messageWithContext.message.contents,
2788
- messageWithContext.local,
2789
- messageWithContext.message.sequenceNumber,
2903
+ message.contents,
2904
+ local,
2905
+ message.sequenceNumber,
2790
2906
  );
2791
2907
  break;
2792
2908
  default: {
2793
- // If we didn't necessarily expect a runtime message type, then no worries - just return
2794
- // e.g. this case applies to system ops, or legacy ops that would have fallen into the above cases anyway.
2795
- if (!messageWithContext.modernRuntimeMessage) {
2796
- return;
2797
- }
2798
-
2799
- const compatBehavior = messageWithContext.message.compatDetails?.behavior;
2800
- if (
2801
- !compatBehaviorAllowsMessageType(messageWithContext.message.type, compatBehavior)
2802
- ) {
2803
- const { message } = messageWithContext;
2909
+ const compatBehavior = message.compatDetails?.behavior;
2910
+ if (!compatBehaviorAllowsMessageType(message.type, compatBehavior)) {
2804
2911
  const error = DataProcessingError.create(
2805
2912
  // Former assert 0x3ce
2806
2913
  "Runtime message of unknown type",
@@ -3982,8 +4089,6 @@ export class ContainerRuntime
3982
4089
  0x93f /* metadata */,
3983
4090
  );
3984
4091
 
3985
- const serializedContent = JSON.stringify(containerRuntimeMessage);
3986
-
3987
4092
  // Note that the real (non-proxy) delta manager is used here to get the readonly info. This is because
3988
4093
  // container runtime's ability to submit ops depend on the actual readonly state of the delta manager.
3989
4094
  if (this.innerDeltaManager.readOnlyInfo.readonly) {
@@ -3998,12 +4103,6 @@ export class ContainerRuntime
3998
4103
  type !== ContainerMessageType.IdAllocation,
3999
4104
  0x9a5 /* IdAllocation should be submitted directly to outbox. */,
4000
4105
  );
4001
- const message: BatchMessage = {
4002
- contents: serializedContent,
4003
- metadata,
4004
- localOpMetadata,
4005
- referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4006
- };
4007
4106
 
4008
4107
  try {
4009
4108
  this.submitIdAllocationOpIfNeeded(false);
@@ -4011,19 +4110,19 @@ export class ContainerRuntime
4011
4110
  // Allow document schema controller to send a message if it needs to propose change in document schema.
4012
4111
  // If it needs to send a message, it will call provided callback with payload of such message and rely
4013
4112
  // on this callback to do actual sending.
4014
- const contents = this.documentsSchemaController.maybeSendSchemaMessage();
4015
- if (contents) {
4113
+ const schemaChangeMessage = this.documentsSchemaController.maybeSendSchemaMessage();
4114
+ if (schemaChangeMessage) {
4016
4115
  this.logger.sendTelemetryEvent({
4017
4116
  eventName: "SchemaChangeProposal",
4018
- refSeq: contents.refSeq,
4019
- version: contents.version,
4020
- newRuntimeSchema: JSON.stringify(contents.runtime),
4117
+ refSeq: schemaChangeMessage.refSeq,
4118
+ version: schemaChangeMessage.version,
4119
+ newRuntimeSchema: JSON.stringify(schemaChangeMessage.runtime),
4021
4120
  sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
4022
4121
  oldRuntimeSchema: JSON.stringify(this.metadata?.documentSchema?.runtime),
4023
4122
  });
4024
4123
  const msg: ContainerRuntimeDocumentSchemaMessage = {
4025
4124
  type: ContainerMessageType.DocumentSchemaChange,
4026
- contents,
4125
+ contents: schemaChangeMessage,
4027
4126
  };
4028
4127
  this.outbox.submit({
4029
4128
  contents: JSON.stringify(msg),
@@ -4031,6 +4130,12 @@ export class ContainerRuntime
4031
4130
  });
4032
4131
  }
4033
4132
 
4133
+ const message: BatchMessage = {
4134
+ contents: JSON.stringify(containerRuntimeMessage) /* serialized content */,
4135
+ metadata,
4136
+ localOpMetadata,
4137
+ referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4138
+ };
4034
4139
  if (type === ContainerMessageType.BlobAttach) {
4035
4140
  // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
4036
4141
  this.outbox.submitBlobAttach(message);
@@ -4130,10 +4235,7 @@ export class ContainerRuntime
4130
4235
 
4131
4236
  // Only include Batch ID if "Offline Load" feature is enabled
4132
4237
  // It's only needed to identify batches across container forks arising from misuse of offline load.
4133
- const includeBatchId =
4134
- this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
4135
-
4136
- this.flush(includeBatchId ? batchId : undefined);
4238
+ this.flush(this.offlineEnabled ? batchId : undefined);
4137
4239
  }
4138
4240
 
4139
4241
  private reSubmit(message: PendingMessageResubmitData) {