@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.2.3.0.115467

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 (161) hide show
  1. package/dist/blobManager.d.ts +20 -5
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +57 -15
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +16 -33
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +71 -219
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.js +2 -2
  10. package/dist/dataStoreContext.js.map +1 -1
  11. package/dist/dataStores.d.ts.map +1 -1
  12. package/dist/dataStores.js +2 -1
  13. package/dist/dataStores.js.map +1 -1
  14. package/dist/garbageCollection.d.ts +7 -16
  15. package/dist/garbageCollection.d.ts.map +1 -1
  16. package/dist/garbageCollection.js +41 -61
  17. package/dist/garbageCollection.js.map +1 -1
  18. package/dist/garbageCollectionConstants.d.ts +19 -0
  19. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  20. package/dist/garbageCollectionConstants.js +34 -0
  21. package/dist/garbageCollectionConstants.js.map +1 -0
  22. package/dist/gcSweepReadyUsageDetection.js +2 -2
  23. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +7 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/opLifecycle/batchManager.d.ts +30 -0
  29. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  30. package/dist/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  31. package/dist/opLifecycle/batchManager.js.map +1 -0
  32. package/dist/opLifecycle/definitions.d.ts +40 -0
  33. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  34. package/dist/opLifecycle/definitions.js +7 -0
  35. package/dist/opLifecycle/definitions.js.map +1 -0
  36. package/dist/opLifecycle/index.d.ts +12 -0
  37. package/dist/opLifecycle/index.d.ts.map +1 -0
  38. package/dist/opLifecycle/index.js +21 -0
  39. package/dist/opLifecycle/index.js.map +1 -0
  40. package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  41. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  42. package/dist/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  43. package/dist/opLifecycle/opCompressor.js.map +1 -0
  44. package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  45. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  46. package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +5 -5
  47. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  48. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  49. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  50. package/dist/opLifecycle/opSplitter.js +61 -0
  51. package/dist/opLifecycle/opSplitter.js.map +1 -0
  52. package/dist/opLifecycle/outbox.d.ts +47 -0
  53. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  54. package/dist/opLifecycle/outbox.js +153 -0
  55. package/dist/opLifecycle/outbox.js.map +1 -0
  56. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  57. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  58. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  59. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/summaryFormat.js +2 -2
  64. package/dist/summaryFormat.js.map +1 -1
  65. package/lib/blobManager.d.ts +20 -5
  66. package/lib/blobManager.d.ts.map +1 -1
  67. package/lib/blobManager.js +59 -17
  68. package/lib/blobManager.js.map +1 -1
  69. package/lib/containerRuntime.d.ts +16 -33
  70. package/lib/containerRuntime.d.ts.map +1 -1
  71. package/lib/containerRuntime.js +68 -215
  72. package/lib/containerRuntime.js.map +1 -1
  73. package/lib/dataStoreContext.js +1 -1
  74. package/lib/dataStoreContext.js.map +1 -1
  75. package/lib/dataStores.d.ts.map +1 -1
  76. package/lib/dataStores.js +2 -1
  77. package/lib/dataStores.js.map +1 -1
  78. package/lib/garbageCollection.d.ts +7 -16
  79. package/lib/garbageCollection.d.ts.map +1 -1
  80. package/lib/garbageCollection.js +19 -39
  81. package/lib/garbageCollection.js.map +1 -1
  82. package/lib/garbageCollectionConstants.d.ts +19 -0
  83. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  84. package/lib/garbageCollectionConstants.js +31 -0
  85. package/lib/garbageCollectionConstants.js.map +1 -0
  86. package/lib/gcSweepReadyUsageDetection.js +1 -1
  87. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  88. package/lib/index.d.ts +4 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +3 -2
  91. package/lib/index.js.map +1 -1
  92. package/lib/opLifecycle/batchManager.d.ts +30 -0
  93. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  94. package/lib/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  95. package/lib/opLifecycle/batchManager.js.map +1 -0
  96. package/lib/opLifecycle/definitions.d.ts +40 -0
  97. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  98. package/lib/opLifecycle/definitions.js +6 -0
  99. package/lib/opLifecycle/definitions.js.map +1 -0
  100. package/lib/opLifecycle/index.d.ts +12 -0
  101. package/lib/opLifecycle/index.d.ts.map +1 -0
  102. package/lib/opLifecycle/index.js +11 -0
  103. package/lib/opLifecycle/index.js.map +1 -0
  104. package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  105. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  106. package/lib/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  107. package/lib/opLifecycle/opCompressor.js.map +1 -0
  108. package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  109. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  110. package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +4 -4
  111. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  112. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  113. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  114. package/lib/opLifecycle/opSplitter.js +57 -0
  115. package/lib/opLifecycle/opSplitter.js.map +1 -0
  116. package/lib/opLifecycle/outbox.d.ts +47 -0
  117. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  118. package/lib/opLifecycle/outbox.js +149 -0
  119. package/lib/opLifecycle/outbox.js.map +1 -0
  120. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  121. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  122. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  123. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  124. package/lib/packageVersion.d.ts +1 -1
  125. package/lib/packageVersion.js +1 -1
  126. package/lib/packageVersion.js.map +1 -1
  127. package/lib/summaryFormat.js +1 -1
  128. package/lib/summaryFormat.js.map +1 -1
  129. package/package.json +21 -34
  130. package/src/blobManager.ts +74 -19
  131. package/src/containerRuntime.ts +91 -278
  132. package/src/dataStoreContext.ts +1 -1
  133. package/src/dataStores.ts +2 -1
  134. package/src/garbageCollection.ts +33 -43
  135. package/src/garbageCollectionConstants.ts +35 -0
  136. package/src/gcSweepReadyUsageDetection.ts +1 -1
  137. package/src/index.ts +5 -4
  138. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +30 -33
  139. package/src/opLifecycle/definitions.ts +44 -0
  140. package/src/opLifecycle/index.ts +17 -0
  141. package/src/{opCompressor.ts → opLifecycle/opCompressor.ts} +21 -16
  142. package/src/{opDecompressor.ts → opLifecycle/opDecompressor.ts} +8 -6
  143. package/src/opLifecycle/opSplitter.ts +78 -0
  144. package/src/opLifecycle/outbox.ts +204 -0
  145. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  146. package/src/packageVersion.ts +1 -1
  147. package/src/summaryFormat.ts +1 -1
  148. package/dist/batchManager.d.ts +0 -42
  149. package/dist/batchManager.d.ts.map +0 -1
  150. package/dist/batchManager.js.map +0 -1
  151. package/dist/opCompressor.d.ts.map +0 -1
  152. package/dist/opCompressor.js.map +0 -1
  153. package/dist/opDecompressor.d.ts.map +0 -1
  154. package/dist/opDecompressor.js.map +0 -1
  155. package/lib/batchManager.d.ts +0 -42
  156. package/lib/batchManager.d.ts.map +0 -1
  157. package/lib/batchManager.js.map +0 -1
  158. package/lib/opCompressor.d.ts.map +0 -1
  159. package/lib/opCompressor.js.map +0 -1
  160. package/lib/opDecompressor.d.ts.map +0 -1
  161. package/lib/opDecompressor.js.map +0 -1
@@ -16,14 +16,12 @@ import {
16
16
  IFluidTokenProvider,
17
17
  IContainerContext,
18
18
  IDeltaManager,
19
- IDeltaSender,
20
19
  IRuntime,
21
20
  ICriticalContainerError,
22
21
  AttachState,
23
22
  ILoaderOptions,
24
23
  LoaderHeader,
25
24
  ISnapshotTreeWithBlobContents,
26
- IBatchMessage,
27
25
  } from "@fluidframework/container-definitions";
28
26
  import {
29
27
  IContainerRuntime,
@@ -119,7 +117,6 @@ import {
119
117
  IPendingLocalState,
120
118
  PendingStateManager,
121
119
  } from "./pendingStateManager";
122
- import { BatchManager, BatchMessage } from "./batchManager";
123
120
  import { pkgVersion } from "./packageVersion";
124
121
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
125
122
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -154,11 +151,13 @@ import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
154
151
  import {
155
152
  GarbageCollector,
156
153
  GCNodeType,
157
- gcTreeKey,
158
154
  IGarbageCollectionRuntime,
159
155
  IGarbageCollector,
160
156
  IGCStats,
161
157
  } from "./garbageCollection";
158
+ import {
159
+ gcTreeKey,
160
+ } from "./garbageCollectionConstants";
162
161
  import {
163
162
  channelToDataStore,
164
163
  IDataStoreAliasMessage,
@@ -167,7 +166,15 @@ import {
167
166
  import { BindBatchTracker } from "./batchTracker";
168
167
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
169
168
  import { ScheduleManager } from "./scheduleManager";
170
- import { OpDecompressor } from "./opDecompressor";
169
+ import {
170
+ BatchMessage,
171
+ IBatchCheckpoint,
172
+ OpCompressor,
173
+ OpDecompressor,
174
+ Outbox,
175
+ OpSplitter,
176
+ RemoteMessageProcessor,
177
+ } from "./opLifecycle";
171
178
 
172
179
  export enum ContainerMessageType {
173
180
  // An op to be delivered to store
@@ -189,16 +196,6 @@ export enum ContainerMessageType {
189
196
  Alias = "alias",
190
197
  }
191
198
 
192
- export interface IChunkedOp {
193
- chunkId: number;
194
-
195
- totalChunks: number;
196
-
197
- contents: string;
198
-
199
- originalType: MessageType | ContainerMessageType;
200
- }
201
-
202
199
  export interface ContainerRuntimeMessage {
203
200
  contents: any;
204
201
  type: ContainerMessageType;
@@ -589,36 +586,6 @@ export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
589
586
  return (Object.values(RuntimeMessage) as string[]).includes(message.type);
590
587
  }
591
588
 
592
- /**
593
- * Unpacks runtime messages
594
- *
595
- * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
596
- * @param message - message (as it observed in storage / service)
597
- * @returns unpacked runtime message
598
- *
599
- * @internal
600
- */
601
- export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
602
- if (message.type === MessageType.Operation) {
603
- // legacy op format?
604
- if (message.contents.address !== undefined && message.contents.type === undefined) {
605
- message.type = ContainerMessageType.FluidDataStoreOp;
606
- } else {
607
- // new format
608
- const innerContents = message.contents as ContainerRuntimeMessage;
609
- message.type = innerContents.type;
610
- message.contents = innerContents.contents;
611
- }
612
- return true;
613
- } else {
614
- // Legacy format, but it's already "unpacked",
615
- // i.e. message.type is actually ContainerMessageType.
616
- // Or it's non-runtime message.
617
- // Nothing to do in such case.
618
- return false;
619
- }
620
- }
621
-
622
589
  /**
623
590
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
624
591
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -661,6 +628,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
661
628
  * @param requestHandler - Request handlers for the container runtime
662
629
  * @param runtimeOptions - Additional options to be passed to the runtime
663
630
  * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
631
+ * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
632
+ * allows mixin classes to leverage this method to define their own async initializer.
664
633
  */
665
634
  public static async load(
666
635
  context: IContainerContext,
@@ -669,6 +638,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
669
638
  runtimeOptions: IContainerRuntimeOptions = {},
670
639
  containerScope: FluidObject = context.scope,
671
640
  existing?: boolean,
641
+ containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntime
672
642
  ): Promise<ContainerRuntime> {
673
643
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
674
644
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -687,8 +657,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
687
657
  loadSequenceNumberVerification = "close",
688
658
  flushMode = defaultFlushMode,
689
659
  enableOfflineLoad = false,
690
- compressionOptions = { minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
691
- compressionAlgorithm: CompressionAlgorithms.lz4 },
660
+ compressionOptions = {
661
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
662
+ compressionAlgorithm: CompressionAlgorithms.lz4
663
+ },
692
664
  maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
693
665
  } = runtimeOptions;
694
666
 
@@ -752,7 +724,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
752
724
  }
753
725
  }
754
726
 
755
- const runtime = new ContainerRuntime(
727
+ const runtime = new containerRuntimeCtor(
756
728
  context,
757
729
  registry,
758
730
  metadata,
@@ -846,8 +818,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
846
818
  // internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
847
819
  private readonly mc: MonitoringContext;
848
820
 
849
- private readonly opDecompressor: OpDecompressor = new OpDecompressor();
850
-
851
821
  private readonly summarizerClientElection?: SummarizerClientElection;
852
822
  /**
853
823
  * summaryManager will only be created if this client is permitted to spawn a summarizing client
@@ -908,27 +878,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
908
878
  * It is created only by summarizing container (i.e. one with clientType === "summarizer")
909
879
  */
910
880
  private readonly _summarizer?: Summarizer;
911
- private readonly deltaSender: IDeltaSender;
912
881
  private readonly scheduleManager: ScheduleManager;
913
882
  private readonly blobManager: BlobManager;
914
883
  private readonly pendingStateManager: PendingStateManager;
915
- private readonly pendingAttachBatch: BatchManager;
916
- private readonly pendingBatch: BatchManager;
884
+ private readonly outbox: Outbox;
917
885
 
918
886
  private readonly garbageCollector: IGarbageCollector;
919
887
 
920
- // Local copy of incomplete received chunks.
921
- private readonly chunkMap: Map<string, string[]>;
922
-
923
888
  private readonly dataStores: DataStores;
889
+ private readonly remoteMessageProcessor: RemoteMessageProcessor;
924
890
 
925
891
  /** The last message processed at the time of the last summary. */
926
892
  private messageAtLastSummary: ISummaryMetadataMessage | undefined;
927
893
 
928
- private get emptyBatch() {
929
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
930
- }
931
-
932
894
  private get summarizer(): Summarizer {
933
895
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
934
896
  return this._summarizer;
@@ -999,7 +961,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
999
961
  */
1000
962
  private nextSummaryNumber: number;
1001
963
 
1002
- private constructor(
964
+ /**
965
+ * @internal
966
+ */
967
+ protected constructor(
1003
968
  private readonly context: IContainerContext,
1004
969
  private readonly registry: IFluidDataStoreRegistry,
1005
970
  metadata: IContainerRuntimeMetadata | undefined,
@@ -1045,7 +1010,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1045
1010
  this.messageAtLastSummary = metadata?.message;
1046
1011
 
1047
1012
  this._connected = this.context.connected;
1048
- this.chunkMap = new Map<string, string[]>(chunks);
1013
+ this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
1049
1014
 
1050
1015
  this.handleContext = new ContainerFluidHandleContext("", this);
1051
1016
 
@@ -1067,21 +1032,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1067
1032
 
1068
1033
  this._flushMode = runtimeOptions.flushMode;
1069
1034
 
1070
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
1071
- // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
1072
- // latency of processing a batch.
1073
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
1074
- // payloads. That number represents final (compressed) bits (once compression is implemented).
1075
- this.pendingAttachBatch = new BatchManager(this.mc.logger, {
1076
- hardLimit: runtimeOptions.maxBatchSizeInBytes,
1077
- softLimit: 64 * 1024,
1078
- compressionOptions: runtimeOptions.compressionOptions
1079
- });
1080
- this.pendingBatch = new BatchManager(this.mc.logger, {
1081
- hardLimit: runtimeOptions.maxBatchSizeInBytes,
1082
- compressionOptions: runtimeOptions.compressionOptions
1083
- });
1084
-
1085
1035
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
1086
1036
  const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
1087
1037
 
@@ -1184,8 +1134,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1184
1134
  ChildLogger.create(this.logger, "ScheduleManager"),
1185
1135
  );
1186
1136
 
1187
- this.deltaSender = this.deltaManager;
1188
-
1189
1137
  this.pendingStateManager = new PendingStateManager(
1190
1138
  {
1191
1139
  applyStashedOp: this.applyStashedOp.bind(this),
@@ -1199,8 +1147,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1199
1147
  },
1200
1148
  pendingRuntimeState?.pending);
1201
1149
 
1150
+ this.outbox = new Outbox({
1151
+ shouldSend: () => this.canSendOps(),
1152
+ pendingStateManager: this.pendingStateManager,
1153
+ containerContext: this.context,
1154
+ compressor: new OpCompressor(this.mc.logger),
1155
+ config: {
1156
+ compressionOptions: runtimeOptions.compressionOptions,
1157
+ maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1158
+ },
1159
+ });
1160
+
1202
1161
  this.context.quorum.on("removeMember", (clientId: string) => {
1203
- this.clearPartialChunks(clientId);
1162
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1204
1163
  });
1205
1164
 
1206
1165
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
@@ -1501,7 +1460,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1501
1460
  addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
1502
1461
  }
1503
1462
 
1504
- private addContainerStateToSummary(
1463
+ protected addContainerStateToSummary(
1505
1464
  summaryTree: ISummaryTreeWithStats,
1506
1465
  fullTree: boolean,
1507
1466
  trackState: boolean,
@@ -1509,8 +1468,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1509
1468
  ) {
1510
1469
  this.addMetadataToSummary(summaryTree);
1511
1470
 
1512
- if (this.chunkMap.size > 0) {
1513
- const content = JSON.stringify([...this.chunkMap]);
1471
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
1472
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
1514
1473
  addBlobToSummary(summaryTree, chunksBlobName, content);
1515
1474
  }
1516
1475
 
@@ -1705,28 +1664,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1705
1664
  public process(messageArg: ISequencedDocumentMessage, local: boolean) {
1706
1665
  this.verifyNotClosed();
1707
1666
 
1708
- // Do shallow copy of message, as methods below will modify it.
1709
- // There might be multiple container instances receiving same message
1710
- // We do not need to make deep copy, as each layer will just replace message.content itself,
1711
- // but would not modify contents details
1712
- let message = { ...messageArg };
1713
-
1714
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
1715
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
1716
- // Old ops may contain empty string (I assume noops).
1717
- if (typeof message.contents === "string" && message.contents !== "") {
1718
- message.contents = JSON.parse(message.contents);
1667
+ if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
1668
+ this.savedOps.push(messageArg);
1719
1669
  }
1720
1670
 
1721
- message = this.opDecompressor.processMessage(message);
1722
1671
 
1723
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
1724
- // This format was not shipped to production workflows.
1725
- const runtimeMessage = unpackRuntimeMessage(message);
1672
+ // Whether or not the message is actually a runtime message.
1673
+ // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
1674
+ // or something different, like a system message.
1675
+ const runtimeMessage = messageArg.type === MessageType.Operation;
1726
1676
 
1727
- if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
1728
- this.savedOps.push(messageArg);
1729
- }
1677
+ // Do shallow copy of message, as the processing flow will modify it.
1678
+ const messageCopy = { ...messageArg };
1679
+ const message = this.remoteMessageProcessor.process(messageCopy);
1730
1680
 
1731
1681
  // Surround the actual processing of the operation with messages to the schedule manager indicating
1732
1682
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
@@ -1734,10 +1684,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1734
1684
  this.scheduleManager.beforeOpProcessing(message);
1735
1685
 
1736
1686
  try {
1737
- // Chunk processing must come first given that we will transform the message to the unchunked version
1738
- // once all pieces are available
1739
- message = this.processRemoteChunkedMessage(message);
1740
-
1741
1687
  let localOpMetadata: unknown;
1742
1688
  if (local && runtimeMessage) {
1743
1689
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1870,97 +1816,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1870
1816
  assert(this._orderSequentiallyCalls === 0,
1871
1817
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1872
1818
 
1873
- this.flushBatch(this.pendingAttachBatch.popBatch());
1874
- this.flushBatch(this.pendingBatch.popBatch());
1875
-
1876
- assert(this.emptyBatch, 0x3cf /* reentrancy */);
1877
- }
1878
-
1879
- protected flushBatch(batch: BatchMessage[]): void {
1880
- const length = batch.length;
1881
-
1882
- if (length > 1) {
1883
- batch[0].metadata = { ...batch[0].metadata, batch: true };
1884
- batch[length - 1].metadata = { ...batch[length - 1].metadata, batch: false };
1885
-
1886
- // This assert fires for the following reason (there might be more cases like that):
1887
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1888
- // i.e. in the middle of op processing!
1889
- // Sending ops while processing ops is not good idea - it's not defined when
1890
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1891
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1892
- // Tracked via ADO #1834
1893
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1894
- // "Batch should be generated synchronously, without processing ops in the middle!");
1895
- }
1896
-
1897
- let clientSequenceNumber: number = -1;
1898
-
1899
- // Did we disconnect in the middle of turn-based batch?
1900
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1901
- if (this.canSendOps()) {
1902
- if (this.context.submitBatchFn !== undefined) {
1903
- const batchToSend: IBatchMessage[] = [];
1904
-
1905
- for (const message of batch) {
1906
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1907
- }
1908
-
1909
- // returns clientSequenceNumber of last message in a batch
1910
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1911
- } else {
1912
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1913
- // version that has support for batches (submitBatchFn)
1914
- for (const message of batch) {
1915
- // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
1916
- if (message.metadata?.compressed) {
1917
- delete message.metadata.compressed;
1918
- }
1919
-
1920
- clientSequenceNumber = this.context.submitFn(
1921
- MessageType.Operation,
1922
- message.deserializedContent,
1923
- true, // batch
1924
- message.metadata);
1925
- }
1926
-
1927
- this.deltaSender.flush();
1928
- }
1929
-
1930
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1931
- clientSequenceNumber -= batch.length - 1;
1932
- assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1933
- }
1934
-
1935
- // Let the PendingStateManager know that a message was submitted.
1936
- // In future, need to shift toward keeping batch as a whole!
1937
- for (const message of batch) {
1938
- this.pendingStateManager.onSubmitMessage(
1939
- message.deserializedContent.type,
1940
- clientSequenceNumber,
1941
- message.referenceSequenceNumber,
1942
- message.deserializedContent.contents,
1943
- message.localOpMetadata,
1944
- message.metadata,
1945
- );
1946
- clientSequenceNumber++;
1947
- }
1948
-
1949
- this.pendingStateManager.onFlush();
1819
+ this.outbox.flush();
1820
+ assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1950
1821
  }
1951
1822
 
1952
- public orderSequentially(callback: () => void): void {
1953
- let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
1954
-
1823
+ public orderSequentially<T>(callback: () => T): T {
1824
+ let checkpoint: IBatchCheckpoint | undefined;
1825
+ let result: T;
1955
1826
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1956
1827
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1957
1828
  // 1. It would not help, as we flush attach ops as they become available.
1958
1829
  // 2. There is no way to undo process of data store creation.
1959
- checkpoint = this.pendingBatch.checkpoint();
1830
+ checkpoint = this.outbox.checkpoint().mainBatch;
1960
1831
  }
1961
1832
  try {
1962
1833
  this._orderSequentiallyCalls++;
1963
- callback();
1834
+ result = callback();
1964
1835
  } catch (error) {
1965
1836
  if (checkpoint) {
1966
1837
  // This will throw and close the container if rollback fails
@@ -1992,6 +1863,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1992
1863
  if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1993
1864
  this.flush();
1994
1865
  }
1866
+ return result;
1995
1867
  }
1996
1868
 
1997
1869
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
@@ -2267,20 +2139,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2267
2139
  // always referenced, so the used routes is only self-route (empty string).
2268
2140
  this.summarizerNode.updateUsedRoutes([""]);
2269
2141
 
2142
+ const blobManagerUsedRoutes: string[] = [];
2270
2143
  const dataStoreUsedRoutes: string[] = [];
2271
2144
  for (const route of usedRoutes) {
2272
- if (route.split("/")[1] !== BlobManager.basePath) {
2145
+ if (this.isBlobPath(route)) {
2146
+ blobManagerUsedRoutes.push(route);
2147
+ } else {
2273
2148
  dataStoreUsedRoutes.push(route);
2274
2149
  }
2275
2150
  }
2276
2151
 
2277
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2152
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
2153
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2278
2154
  }
2279
2155
 
2280
2156
  /**
2281
2157
  * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
2282
2158
  * tombstones.
2283
- * @param unusedRoutes - The routes that are unused in all data stores in this Container.
2159
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
2284
2160
  * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
2285
2161
  * are deleted.
2286
2162
  */
@@ -2295,10 +2171,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2295
2171
  }
2296
2172
  }
2297
2173
 
2298
- // Todo: Add tombstone for attachment blobs. For now, we ignore attachment blobs that should be tombstoned.
2299
- if (!tombstone) {
2300
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2301
- }
2174
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
2302
2175
  this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
2303
2176
  }
2304
2177
 
@@ -2397,7 +2270,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2397
2270
  },
2398
2271
  );
2399
2272
 
2400
- assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2273
+ assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2401
2274
 
2402
2275
  let latestSnapshotVersionId: string | undefined;
2403
2276
  if (refreshLatestAck) {
@@ -2607,45 +2480,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2607
2480
  }
2608
2481
  }
2609
2482
 
2610
- private processRemoteChunkedMessage(message: ISequencedDocumentMessage) {
2611
- if (message.type !== ContainerMessageType.ChunkedOp) {
2612
- return message;
2613
- }
2614
-
2615
- const clientId = message.clientId;
2616
- const chunkedContent = message.contents as IChunkedOp;
2617
- this.addChunk(clientId, chunkedContent);
2618
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
2619
- const newMessage = { ...message };
2620
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2621
- const serializedContent = this.chunkMap.get(clientId)!.join("");
2622
- newMessage.contents = JSON.parse(serializedContent);
2623
- newMessage.type = chunkedContent.originalType;
2624
- this.clearPartialChunks(clientId);
2625
- return newMessage;
2626
- }
2627
- return message;
2628
- }
2629
-
2630
- private addChunk(clientId: string, chunkedContent: IChunkedOp) {
2631
- let map = this.chunkMap.get(clientId);
2632
- if (map === undefined) {
2633
- map = [];
2634
- this.chunkMap.set(clientId, map);
2635
- }
2636
- assert(chunkedContent.chunkId === map.length + 1,
2637
- 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
2638
- map.push(chunkedContent.contents);
2639
- }
2640
-
2641
- private clearPartialChunks(clientId: string) {
2642
- if (this.chunkMap.has(clientId)) {
2643
- this.chunkMap.delete(clientId);
2644
- }
2645
- }
2646
-
2647
2483
  private hasPendingMessages() {
2648
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
2484
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
2649
2485
  }
2650
2486
 
2651
2487
  private updateDocumentDirtyState(dirty: boolean) {
@@ -2708,7 +2544,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2708
2544
  const serializedContent = JSON.stringify(deserializedContent);
2709
2545
 
2710
2546
  if (this.deltaManager.readOnlyInfo.readonly) {
2711
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
2547
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
2712
2548
  }
2713
2549
 
2714
2550
  const message: BatchMessage = {
@@ -2741,45 +2577,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2741
2577
  // Please note that this does not change file format, so it can be disabled in the future if this
2742
2578
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
2743
2579
  if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
2744
- this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
2745
- if (!this.pendingAttachBatch.push(message)) {
2746
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
2747
- // when queue is not empty.
2748
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
2749
- this.flushBatch(this.pendingAttachBatch.popBatch());
2750
- if (!this.pendingAttachBatch.push(message)) {
2751
- throw new GenericError(
2752
- "BatchTooLarge",
2753
- /* error */ undefined,
2754
- {
2755
- opSize: (message.contents?.length) ?? 0,
2756
- count: this.pendingAttachBatch.length,
2757
- limit: this.pendingAttachBatch.options.hardLimit,
2758
- });
2759
- }
2760
- }
2580
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
2581
+ this.outbox.submitAttach(message);
2761
2582
  } else {
2762
- if (!this.pendingBatch.push(message)) {
2763
- throw new GenericError(
2764
- "BatchTooLarge",
2765
- /* error */ undefined,
2766
- {
2767
- opSize: (message.contents?.length) ?? 0,
2768
- count: this.pendingBatch.length,
2769
- limit: this.pendingBatch.options.hardLimit,
2770
- });
2771
- }
2772
- if (!this.currentlyBatching()) {
2583
+ this.outbox.submit(message);
2584
+ }
2585
+
2586
+ if (!this.currentlyBatching()) {
2587
+ this.flush();
2588
+ } else if (!this.flushMicroTaskExists) {
2589
+ this.flushMicroTaskExists = true;
2590
+ // Queue a microtask to detect the end of the turn and force a flush.
2591
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
2592
+ Promise.resolve().then(() => {
2593
+ this.flushMicroTaskExists = false;
2773
2594
  this.flush();
2774
- } else if (!this.flushMicroTaskExists) {
2775
- this.flushMicroTaskExists = true;
2776
- // Queue a microtask to detect the end of the turn and force a flush.
2777
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
2778
- Promise.resolve().then(() => {
2779
- this.flushMicroTaskExists = false;
2780
- this.flush();
2781
- });
2782
- }
2595
+ }).catch((error) => { this.closeFn(error as GenericError) });
2783
2596
  }
2784
2597
  } catch (error) {
2785
2598
  this.closeFn(error as GenericError);
@@ -2796,7 +2609,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2796
2609
  assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
2797
2610
 
2798
2611
  // System message should not be sent in the middle of the batch.
2799
- assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
2612
+ assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
2800
2613
 
2801
2614
  // back-compat: ADO #1385: Make this call unconditional in the future
2802
2615
  return this.context.submitSummaryFn !== undefined
@@ -2908,12 +2721,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2908
2721
 
2909
2722
  const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2910
2723
  summaryLogger.sendTelemetryEvent(
2911
- {
2912
- eventName: "LatestSummaryRetrieved",
2913
- ackHandle,
2914
- lastSequenceNumber: latestSnapshotRefSeq,
2915
- targetSequenceNumber: summaryRefSeq,
2916
- });
2724
+ {
2725
+ eventName: "LatestSummaryRetrieved",
2726
+ ackHandle,
2727
+ lastSequenceNumber: latestSnapshotRefSeq,
2728
+ targetSequenceNumber: summaryRefSeq,
2729
+ });
2917
2730
 
2918
2731
  // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
2919
2732
  // wait for the delta manager to catch up before refreshing the latest Summary.
@@ -87,7 +87,7 @@ import {
87
87
  getAttributesFormatVersion,
88
88
  getFluidDataStoreAttributes,
89
89
  } from "./summaryFormat";
90
- import { throwOnTombstoneUsageKey } from "./garbageCollection";
90
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
91
91
  import { summarizerClientType } from "./summarizerClientElection";
92
92
 
93
93
  function createAttributes(
package/src/dataStores.ts CHANGED
@@ -54,7 +54,8 @@ import {
54
54
  } from "./dataStoreContext";
55
55
  import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
56
56
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
57
- import { throwOnTombstoneUsageKey, GCNodeType } from "./garbageCollection";
57
+ import { GCNodeType } from "./garbageCollection";
58
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
58
59
  import { summarizerClientType } from "./summarizerClientElection";
59
60
 
60
61
  type PendingAliasResolve = (success: boolean) => void;