@fluidframework/container-runtime 2.0.0-internal.2.1.1 → 2.0.0-internal.2.2.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 (161) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +20 -5
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +57 -15
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +39 -40
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +115 -278
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStoreContext.d.ts +3 -1
  11. package/dist/dataStoreContext.d.ts.map +1 -1
  12. package/dist/dataStoreContext.js +21 -3
  13. package/dist/dataStoreContext.js.map +1 -1
  14. package/dist/dataStores.d.ts +8 -5
  15. package/dist/dataStores.d.ts.map +1 -1
  16. package/dist/dataStores.js +26 -13
  17. package/dist/dataStores.js.map +1 -1
  18. package/dist/garbageCollection.d.ts +15 -17
  19. package/dist/garbageCollection.d.ts.map +1 -1
  20. package/dist/garbageCollection.js +92 -106
  21. package/dist/garbageCollection.js.map +1 -1
  22. package/dist/garbageCollectionConstants.d.ts +19 -0
  23. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  24. package/dist/garbageCollectionConstants.js +34 -0
  25. package/dist/garbageCollectionConstants.js.map +1 -0
  26. package/dist/gcSweepReadyUsageDetection.js +2 -2
  27. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -6
  31. package/dist/index.js.map +1 -1
  32. package/dist/opLifecycle/batchManager.d.ts +30 -0
  33. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  34. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -10
  35. package/dist/opLifecycle/batchManager.js.map +1 -0
  36. package/dist/opLifecycle/definitions.d.ts +40 -0
  37. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  38. package/dist/opLifecycle/definitions.js +7 -0
  39. package/dist/opLifecycle/definitions.js.map +1 -0
  40. package/dist/opLifecycle/index.d.ts +12 -0
  41. package/dist/opLifecycle/index.d.ts.map +1 -0
  42. package/dist/opLifecycle/index.js +21 -0
  43. package/dist/opLifecycle/index.js.map +1 -0
  44. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  45. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  46. package/dist/opLifecycle/opCompressor.js +53 -0
  47. package/dist/opLifecycle/opCompressor.js.map +1 -0
  48. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  49. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  50. package/dist/opLifecycle/opDecompressor.js +72 -0
  51. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  52. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  53. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  54. package/dist/opLifecycle/opSplitter.js +61 -0
  55. package/dist/opLifecycle/opSplitter.js.map +1 -0
  56. package/dist/opLifecycle/outbox.d.ts +47 -0
  57. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  58. package/dist/opLifecycle/outbox.js +153 -0
  59. package/dist/opLifecycle/outbox.js.map +1 -0
  60. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  61. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  62. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  63. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  64. package/dist/packageVersion.d.ts +1 -1
  65. package/dist/packageVersion.js +1 -1
  66. package/dist/packageVersion.js.map +1 -1
  67. package/dist/summaryFormat.js +2 -2
  68. package/dist/summaryFormat.js.map +1 -1
  69. package/lib/blobManager.d.ts +20 -5
  70. package/lib/blobManager.d.ts.map +1 -1
  71. package/lib/blobManager.js +59 -17
  72. package/lib/blobManager.js.map +1 -1
  73. package/lib/containerRuntime.d.ts +39 -40
  74. package/lib/containerRuntime.d.ts.map +1 -1
  75. package/lib/containerRuntime.js +113 -275
  76. package/lib/containerRuntime.js.map +1 -1
  77. package/lib/dataStoreContext.d.ts +3 -1
  78. package/lib/dataStoreContext.d.ts.map +1 -1
  79. package/lib/dataStoreContext.js +23 -5
  80. package/lib/dataStoreContext.js.map +1 -1
  81. package/lib/dataStores.d.ts +8 -5
  82. package/lib/dataStores.d.ts.map +1 -1
  83. package/lib/dataStores.js +28 -15
  84. package/lib/dataStores.js.map +1 -1
  85. package/lib/garbageCollection.d.ts +15 -17
  86. package/lib/garbageCollection.d.ts.map +1 -1
  87. package/lib/garbageCollection.js +72 -86
  88. package/lib/garbageCollection.js.map +1 -1
  89. package/lib/garbageCollectionConstants.d.ts +19 -0
  90. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  91. package/lib/garbageCollectionConstants.js +31 -0
  92. package/lib/garbageCollectionConstants.js.map +1 -0
  93. package/lib/gcSweepReadyUsageDetection.js +1 -1
  94. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  95. package/lib/index.d.ts +4 -2
  96. package/lib/index.d.ts.map +1 -1
  97. package/lib/index.js +3 -2
  98. package/lib/index.js.map +1 -1
  99. package/lib/opLifecycle/batchManager.d.ts +30 -0
  100. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  101. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -10
  102. package/lib/opLifecycle/batchManager.js.map +1 -0
  103. package/lib/opLifecycle/definitions.d.ts +40 -0
  104. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  105. package/lib/opLifecycle/definitions.js +6 -0
  106. package/lib/opLifecycle/definitions.js.map +1 -0
  107. package/lib/opLifecycle/index.d.ts +12 -0
  108. package/lib/opLifecycle/index.d.ts.map +1 -0
  109. package/lib/opLifecycle/index.js +11 -0
  110. package/lib/opLifecycle/index.js.map +1 -0
  111. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  112. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  113. package/lib/opLifecycle/opCompressor.js +49 -0
  114. package/lib/opLifecycle/opCompressor.js.map +1 -0
  115. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  116. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  117. package/lib/opLifecycle/opDecompressor.js +68 -0
  118. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  119. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  120. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  121. package/lib/opLifecycle/opSplitter.js +57 -0
  122. package/lib/opLifecycle/opSplitter.js.map +1 -0
  123. package/lib/opLifecycle/outbox.d.ts +47 -0
  124. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  125. package/lib/opLifecycle/outbox.js +149 -0
  126. package/lib/opLifecycle/outbox.js.map +1 -0
  127. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  128. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  129. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  130. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  131. package/lib/packageVersion.d.ts +1 -1
  132. package/lib/packageVersion.js +1 -1
  133. package/lib/packageVersion.js.map +1 -1
  134. package/lib/summaryFormat.js +1 -1
  135. package/lib/summaryFormat.js.map +1 -1
  136. package/package.json +35 -21
  137. package/prettier.config.cjs +8 -0
  138. package/src/blobManager.ts +74 -19
  139. package/src/containerRuntime.ts +144 -341
  140. package/src/dataStoreContext.ts +33 -5
  141. package/src/dataStores.ts +32 -16
  142. package/src/garbageCollection.ts +106 -82
  143. package/src/garbageCollectionConstants.ts +35 -0
  144. package/src/gcSweepReadyUsageDetection.ts +1 -1
  145. package/src/index.ts +6 -4
  146. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +41 -23
  147. package/src/opLifecycle/definitions.ts +44 -0
  148. package/src/opLifecycle/index.ts +17 -0
  149. package/src/opLifecycle/opCompressor.ts +64 -0
  150. package/src/opLifecycle/opDecompressor.ts +84 -0
  151. package/src/opLifecycle/opSplitter.ts +78 -0
  152. package/src/opLifecycle/outbox.ts +204 -0
  153. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  154. package/src/packageVersion.ts +1 -1
  155. package/src/summaryFormat.ts +1 -1
  156. package/dist/batchManager.d.ts +0 -36
  157. package/dist/batchManager.d.ts.map +0 -1
  158. package/dist/batchManager.js.map +0 -1
  159. package/lib/batchManager.d.ts +0 -36
  160. package/lib/batchManager.d.ts.map +0 -1
  161. package/lib/batchManager.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,
@@ -34,7 +32,6 @@ import {
34
32
  Trace,
35
33
  TypedEventEmitter,
36
34
  unreachableCase,
37
- IsoBuffer,
38
35
  } from "@fluidframework/common-utils";
39
36
  import {
40
37
  ChildLogger,
@@ -108,7 +105,6 @@ import {
108
105
  } from "@fluidframework/runtime-utils";
109
106
  import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
110
107
  import { v4 as uuid } from "uuid";
111
- import { compress, decompress } from "lz4js";
112
108
  import { ContainerFluidHandleContext } from "./containerHandleContext";
113
109
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
114
110
  import { Summarizer } from "./summarizer";
@@ -121,7 +117,6 @@ import {
121
117
  IPendingLocalState,
122
118
  PendingStateManager,
123
119
  } from "./pendingStateManager";
124
- import { BatchManager, BatchMessage } from "./batchManager";
125
120
  import { pkgVersion } from "./packageVersion";
126
121
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
127
122
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -156,12 +151,13 @@ import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
156
151
  import {
157
152
  GarbageCollector,
158
153
  GCNodeType,
159
- gcTreeKey,
160
154
  IGarbageCollectionRuntime,
161
155
  IGarbageCollector,
162
156
  IGCStats,
163
- testTombstoneKey,
164
157
  } from "./garbageCollection";
158
+ import {
159
+ gcTreeKey,
160
+ } from "./garbageCollectionConstants";
165
161
  import {
166
162
  channelToDataStore,
167
163
  IDataStoreAliasMessage,
@@ -170,6 +166,15 @@ import {
170
166
  import { BindBatchTracker } from "./batchTracker";
171
167
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
172
168
  import { ScheduleManager } from "./scheduleManager";
169
+ import {
170
+ BatchMessage,
171
+ IBatchCheckpoint,
172
+ OpCompressor,
173
+ OpDecompressor,
174
+ Outbox,
175
+ OpSplitter,
176
+ RemoteMessageProcessor,
177
+ } from "./opLifecycle";
173
178
 
174
179
  export enum ContainerMessageType {
175
180
  // An op to be delivered to store
@@ -191,16 +196,6 @@ export enum ContainerMessageType {
191
196
  Alias = "alias",
192
197
  }
193
198
 
194
- export interface IChunkedOp {
195
- chunkId: number;
196
-
197
- totalChunks: number;
198
-
199
- contents: string;
200
-
201
- originalType: MessageType | ContainerMessageType;
202
- }
203
-
204
199
  export interface ContainerRuntimeMessage {
205
200
  contents: any;
206
201
  type: ContainerMessageType;
@@ -213,6 +208,7 @@ export interface ISummaryBaseConfiguration {
213
208
  initialSummarizerDelayMs: number;
214
209
 
215
210
  /**
211
+ * @deprecated
216
212
  * Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
217
213
  * This defaults to false (disabled) and must be explicitly set to true to enable.
218
214
  */
@@ -434,10 +430,14 @@ export interface ISummaryRuntimeOptions {
434
430
  */
435
431
  export interface ICompressionRuntimeOptions {
436
432
  /**
437
- * The minimum size the content payload must exceed before it is compressed.
438
- * Compression is disabled if undefined.
433
+ * The minimum size the batch's payload must exceed before the batch's contents will be compressed.
439
434
  */
440
- readonly minimumSize?: number;
435
+ readonly minimumBatchSizeInBytes: number;
436
+
437
+ /**
438
+ * The compression algorithm that will be used to compress the op.
439
+ */
440
+ readonly compressionAlgorithm: CompressionAlgorithms;
441
441
  }
442
442
 
443
443
  /**
@@ -467,7 +467,7 @@ export interface IContainerRuntimeOptions {
467
467
  */
468
468
  readonly enableOfflineLoad?: boolean;
469
469
  /**
470
- * Enables the runtime to compress ops.
470
+ * Enables the runtime to compress ops. Compression is disabled when undefined.
471
471
  * @experimental Not ready for use.
472
472
  */
473
473
  readonly compressionOptions?: ICompressionRuntimeOptions;
@@ -507,6 +507,13 @@ export enum RuntimeHeaders {
507
507
  viaHandle = "viaHandle",
508
508
  }
509
509
 
510
+ /**
511
+ * Available compression algorithms for op compression.
512
+ */
513
+ export enum CompressionAlgorithms {
514
+ lz4 = "lz4",
515
+ }
516
+
510
517
  /**
511
518
  * @deprecated
512
519
  * Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
@@ -579,46 +586,6 @@ export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
579
586
  return (Object.values(RuntimeMessage) as string[]).includes(message.type);
580
587
  }
581
588
 
582
- /**
583
- * Unpacks runtime messages
584
- *
585
- * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
586
- * @param message - message (as it observed in storage / service)
587
- * @returns unpacked runtime message
588
- *
589
- * @internal
590
- */
591
- export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
592
- if (message.metadata?.compressed) {
593
- const contents = IsoBuffer.from(message.contents.contents, "base64");
594
- const decompressedMessage = decompress(contents);
595
- const intoString = new TextDecoder().decode(decompressedMessage);
596
- const asObj = JSON.parse(intoString);
597
- message.contents.contents = asObj;
598
- message.metadata.compressed = false;
599
- }
600
-
601
- if (message.type === MessageType.Operation) {
602
- // legacy op format?
603
- if (message.contents.address !== undefined && message.contents.type === undefined) {
604
- message.type = ContainerMessageType.FluidDataStoreOp;
605
- } else {
606
- // new format
607
- const innerContents = message.contents as ContainerRuntimeMessage;
608
- assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
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,7 +657,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
687
657
  loadSequenceNumberVerification = "close",
688
658
  flushMode = defaultFlushMode,
689
659
  enableOfflineLoad = false,
690
- compressionOptions = {},
660
+ compressionOptions = {
661
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
662
+ compressionAlgorithm: CompressionAlgorithms.lz4
663
+ },
691
664
  maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
692
665
  } = runtimeOptions;
693
666
 
@@ -751,7 +724,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
751
724
  }
752
725
  }
753
726
 
754
- const runtime = new ContainerRuntime(
727
+ const runtime = new containerRuntimeCtor(
755
728
  context,
756
729
  registry,
757
730
  metadata,
@@ -781,7 +754,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
781
754
  pendingRuntimeState.savedOps = [];
782
755
  }
783
756
 
784
- await runtime.getSnapshotBlobs();
757
+ // Initialize the base state of the runtime before it's returned.
758
+ await runtime.initializeBaseState();
785
759
 
786
760
  return runtime;
787
761
  }
@@ -843,6 +817,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
843
817
 
844
818
  // internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
845
819
  private readonly mc: MonitoringContext;
820
+
846
821
  private readonly summarizerClientElection?: SummarizerClientElection;
847
822
  /**
848
823
  * summaryManager will only be created if this client is permitted to spawn a summarizing client
@@ -867,7 +842,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
867
842
  private baseSnapshotBlobs?: ISerializedBaseSnapshotBlobs;
868
843
 
869
844
  private consecutiveReconnects = 0;
870
- private compressedOpCount = 0;
871
845
 
872
846
  /**
873
847
  * Used to delay transition to "connected" state while we upload
@@ -904,27 +878,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
904
878
  * It is created only by summarizing container (i.e. one with clientType === "summarizer")
905
879
  */
906
880
  private readonly _summarizer?: Summarizer;
907
- private readonly deltaSender: IDeltaSender;
908
881
  private readonly scheduleManager: ScheduleManager;
909
882
  private readonly blobManager: BlobManager;
910
883
  private readonly pendingStateManager: PendingStateManager;
911
- private readonly pendingAttachBatch: BatchManager;
912
- private readonly pendingBatch: BatchManager;
884
+ private readonly outbox: Outbox;
913
885
 
914
886
  private readonly garbageCollector: IGarbageCollector;
915
887
 
916
- // Local copy of incomplete received chunks.
917
- private readonly chunkMap: Map<string, string[]>;
918
-
919
888
  private readonly dataStores: DataStores;
889
+ private readonly remoteMessageProcessor: RemoteMessageProcessor;
920
890
 
921
891
  /** The last message processed at the time of the last summary. */
922
892
  private messageAtLastSummary: ISummaryMetadataMessage | undefined;
923
893
 
924
- private get emptyBatch() {
925
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
926
- }
927
-
928
894
  private get summarizer(): Summarizer {
929
895
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
930
896
  return this._summarizer;
@@ -995,7 +961,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
995
961
  */
996
962
  private nextSummaryNumber: number;
997
963
 
998
- private constructor(
964
+ /**
965
+ * @internal
966
+ */
967
+ protected constructor(
999
968
  private readonly context: IContainerContext,
1000
969
  private readonly registry: IFluidDataStoreRegistry,
1001
970
  metadata: IContainerRuntimeMetadata | undefined,
@@ -1017,10 +986,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1017
986
  },
1018
987
  ) {
1019
988
  super();
989
+
990
+ let loadSummaryNumber: number;
991
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
992
+ // get the values from the metadata blob.
993
+ if (existing) {
994
+ this.createContainerMetadata = {
995
+ createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
996
+ createContainerTimestamp: metadata?.createContainerTimestamp,
997
+ };
998
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
999
+ // the count is reset to 0.
1000
+ loadSummaryNumber = metadata?.summaryNumber ?? 0;
1001
+ } else {
1002
+ this.createContainerMetadata = {
1003
+ createContainerRuntimeVersion: pkgVersion,
1004
+ createContainerTimestamp: Date.now(),
1005
+ };
1006
+ loadSummaryNumber = 0;
1007
+ }
1008
+ this.nextSummaryNumber = loadSummaryNumber + 1;
1009
+
1020
1010
  this.messageAtLastSummary = metadata?.message;
1021
1011
 
1022
1012
  this._connected = this.context.connected;
1023
- this.chunkMap = new Map<string, string[]>(chunks);
1013
+ this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
1024
1014
 
1025
1015
  this.handleContext = new ContainerFluidHandleContext("", this);
1026
1016
 
@@ -1042,14 +1032,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1042
1032
 
1043
1033
  this._flushMode = runtimeOptions.flushMode;
1044
1034
 
1045
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
1046
- // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
1047
- // latency of processing a batch.
1048
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
1049
- // payloads. That number represents final (compressed) bits (once compression is implemented).
1050
- this.pendingAttachBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
1051
- this.pendingBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes);
1052
-
1053
1035
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
1054
1036
  const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
1055
1037
 
@@ -1068,6 +1050,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1068
1050
  baseLogger: this.mc.logger,
1069
1051
  existing,
1070
1052
  metadata,
1053
+ createContainerMetadata: this.createContainerMetadata,
1071
1054
  isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
1072
1055
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1073
1056
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
@@ -1151,8 +1134,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1151
1134
  ChildLogger.create(this.logger, "ScheduleManager"),
1152
1135
  );
1153
1136
 
1154
- this.deltaSender = this.deltaManager;
1155
-
1156
1137
  this.pendingStateManager = new PendingStateManager(
1157
1138
  {
1158
1139
  applyStashedOp: this.applyStashedOp.bind(this),
@@ -1166,8 +1147,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1166
1147
  },
1167
1148
  pendingRuntimeState?.pending);
1168
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
+
1169
1161
  this.context.quorum.on("removeMember", (clientId: string) => {
1170
- this.clearPartialChunks(clientId);
1162
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1171
1163
  });
1172
1164
 
1173
1165
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
@@ -1283,26 +1275,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1283
1275
  ...getDeviceSpec(),
1284
1276
  });
1285
1277
 
1286
- let loadSummaryNumber: number;
1287
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
1288
- // get the values from the metadata blob.
1289
- if (existing) {
1290
- this.createContainerMetadata = {
1291
- createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
1292
- createContainerTimestamp: metadata?.createContainerTimestamp,
1293
- };
1294
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
1295
- // the count is reset to 0.
1296
- loadSummaryNumber = metadata?.summaryNumber ?? 0;
1297
- } else {
1298
- this.createContainerMetadata = {
1299
- createContainerRuntimeVersion: pkgVersion,
1300
- createContainerTimestamp: Date.now(),
1301
- };
1302
- loadSummaryNumber = 0;
1303
- }
1304
- this.nextSummaryNumber = loadSummaryNumber + 1;
1305
-
1306
1278
  this.logger.sendTelemetryEvent({
1307
1279
  eventName: "ContainerLoadStats",
1308
1280
  ...this.createContainerMetadata,
@@ -1317,6 +1289,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1317
1289
  BindBatchTracker(this, this.logger);
1318
1290
  }
1319
1291
 
1292
+ /**
1293
+ * Initializes the state from the base snapshot this container runtime loaded from.
1294
+ */
1295
+ private async initializeBaseState(): Promise<void> {
1296
+ await this.initializeBaseSnapshotBlobs();
1297
+ await this.garbageCollector.initializeBaseState();
1298
+ }
1299
+
1320
1300
  public dispose(error?: Error): void {
1321
1301
  if (this._disposed) {
1322
1302
  return;
@@ -1480,7 +1460,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1480
1460
  addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
1481
1461
  }
1482
1462
 
1483
- private addContainerStateToSummary(
1463
+ protected addContainerStateToSummary(
1484
1464
  summaryTree: ISummaryTreeWithStats,
1485
1465
  fullTree: boolean,
1486
1466
  trackState: boolean,
@@ -1488,8 +1468,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1488
1468
  ) {
1489
1469
  this.addMetadataToSummary(summaryTree);
1490
1470
 
1491
- if (this.chunkMap.size > 0) {
1492
- const content = JSON.stringify([...this.chunkMap]);
1471
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
1472
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
1493
1473
  addBlobToSummary(summaryTree, chunksBlobName, content);
1494
1474
  }
1495
1475
 
@@ -1684,37 +1664,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1684
1664
  public process(messageArg: ISequencedDocumentMessage, local: boolean) {
1685
1665
  this.verifyNotClosed();
1686
1666
 
1687
- // Do shallow copy of message, as methods below will modify it.
1688
- // There might be multiple container instances receiving same message
1689
- // We do not need to make deep copy, as each layer will just replace message.content itself,
1690
- // but would not modify contents details
1691
- let message = { ...messageArg };
1692
-
1693
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
1694
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
1695
- // Old ops may contain empty string (I assume noops).
1696
- if (typeof message.contents === "string" && message.contents !== "") {
1697
- message.contents = JSON.parse(message.contents);
1698
- }
1699
-
1700
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
1701
- // This format was not shipped to production workflows.
1702
- const runtimeMessage = unpackRuntimeMessage(message);
1703
-
1704
1667
  if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
1705
1668
  this.savedOps.push(messageArg);
1706
1669
  }
1707
1670
 
1671
+
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;
1676
+
1677
+ // Do shallow copy of message, as the processing flow will modify it.
1678
+ const messageCopy = { ...messageArg };
1679
+ const message = this.remoteMessageProcessor.process(messageCopy);
1680
+
1708
1681
  // Surround the actual processing of the operation with messages to the schedule manager indicating
1709
1682
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
1710
1683
  // messages once a batch has been fully processed.
1711
1684
  this.scheduleManager.beforeOpProcessing(message);
1712
1685
 
1713
1686
  try {
1714
- // Chunk processing must come first given that we will transform the message to the unchunked version
1715
- // once all pieces are available
1716
- message = this.processRemoteChunkedMessage(message);
1717
-
1718
1687
  let localOpMetadata: unknown;
1719
1688
  if (local && runtimeMessage) {
1720
1689
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1847,117 +1816,18 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1847
1816
  assert(this._orderSequentiallyCalls === 0,
1848
1817
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1849
1818
 
1850
- this.flushBatch(this.pendingAttachBatch.popBatch());
1851
- this.flushBatch(this.pendingBatch.popBatch());
1852
-
1853
- assert(this.emptyBatch, 0x3cf /* reentrancy */);
1854
- }
1855
-
1856
- protected flushBatch(batch: BatchMessage[]): void {
1857
- const length = batch.length;
1858
-
1859
- if (length > 1) {
1860
- batch[0].metadata = { ...batch[0].metadata, batch: true };
1861
- batch[length - 1].metadata = { ...batch[length - 1].metadata, batch: false };
1862
-
1863
- // This assert fires for the following reason (there might be more cases like that):
1864
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1865
- // i.e. in the middle of op processing!
1866
- // Sending ops while processing ops is not good idea - it's not defined when
1867
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1868
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1869
- // Tracked via ADO #1834
1870
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1871
- // "Batch should be generated synchronously, without processing ops in the middle!");
1872
- }
1873
-
1874
- let clientSequenceNumber: number = -1;
1875
-
1876
- // Did we disconnect in the middle of turn-based batch?
1877
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1878
- if (this.canSendOps()) {
1879
- if (this.context.submitBatchFn !== undefined) {
1880
- const batchToSend: IBatchMessage[] = [];
1881
- for (const message of batch) {
1882
- let contents = message.contents;
1883
- let metadata = message.metadata;
1884
- if (this.runtimeOptions.compressionOptions.minimumSize &&
1885
- this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
1886
- this.compressedOpCount++;
1887
- const copiedMessage = { ...message.deserializedContent };
1888
-
1889
- const compressionStart = Date.now();
1890
- const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
1891
- const compressedContents = compress(contentsAsBuffer);
1892
- const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
1893
- const duration = Date.now() - compressionStart;
1894
-
1895
- if (this.compressedOpCount % 100) {
1896
- this.mc.logger.sendPerformanceEvent({
1897
- eventName: "compressedOp",
1898
- duration,
1899
- sizeBeforeCompression: message.contents.length,
1900
- sizeAfterCompression: compressedContent.length,
1901
- });
1902
- }
1903
-
1904
- copiedMessage.contents = compressedContent;
1905
- const stringifiedContents = JSON.stringify(copiedMessage);
1906
-
1907
- if (stringifiedContents.length < message.contents.length) {
1908
- contents = JSON.stringify(copiedMessage);
1909
- metadata = { ...message.metadata, compressed: true };
1910
- }
1911
- }
1912
-
1913
- batchToSend.push({ contents, metadata });
1914
- }
1915
- // returns clientSequenceNumber of last message in a batch
1916
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1917
- } else {
1918
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1919
- // version that has support for batches (submitBatchFn)
1920
- for (const message of batch) {
1921
- clientSequenceNumber = this.context.submitFn(
1922
- MessageType.Operation,
1923
- message.deserializedContent,
1924
- true, // batch
1925
- message.metadata);
1926
- }
1927
-
1928
- this.deltaSender.flush();
1929
- }
1930
-
1931
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1932
- clientSequenceNumber -= batch.length - 1;
1933
- assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1934
- }
1935
-
1936
- // Let the PendingStateManager know that a message was submitted.
1937
- // In future, need to shift toward keeping batch as a whole!
1938
- for (const message of batch) {
1939
- this.pendingStateManager.onSubmitMessage(
1940
- message.deserializedContent.type,
1941
- clientSequenceNumber,
1942
- message.referenceSequenceNumber,
1943
- message.deserializedContent.contents,
1944
- message.localOpMetadata,
1945
- message.metadata,
1946
- );
1947
- clientSequenceNumber++;
1948
- }
1949
-
1950
- this.pendingStateManager.onFlush();
1819
+ this.outbox.flush();
1820
+ assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1951
1821
  }
1952
1822
 
1953
1823
  public orderSequentially(callback: () => void): void {
1954
- let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
1824
+ let checkpoint: IBatchCheckpoint | undefined;
1955
1825
 
1956
1826
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1957
1827
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1958
1828
  // 1. It would not help, as we flush attach ops as they become available.
1959
1829
  // 2. There is no way to undo process of data store creation.
1960
- checkpoint = this.pendingBatch.checkpoint();
1830
+ checkpoint = this.outbox.checkpoint().mainBatch;
1961
1831
  }
1962
1832
  try {
1963
1833
  this._orderSequentiallyCalls++;
@@ -2268,35 +2138,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2268
2138
  // always referenced, so the used routes is only self-route (empty string).
2269
2139
  this.summarizerNode.updateUsedRoutes([""]);
2270
2140
 
2141
+ const blobManagerUsedRoutes: string[] = [];
2271
2142
  const dataStoreUsedRoutes: string[] = [];
2272
2143
  for (const route of usedRoutes) {
2273
- if (route.split("/")[1] !== BlobManager.basePath) {
2144
+ if (this.isBlobPath(route)) {
2145
+ blobManagerUsedRoutes.push(route);
2146
+ } else {
2274
2147
  dataStoreUsedRoutes.push(route);
2275
2148
  }
2276
2149
  }
2277
2150
 
2278
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2151
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
2152
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2279
2153
  }
2280
2154
 
2281
2155
  /**
2282
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
2283
- * scenarios with accessing deleted content.
2284
- * @param unusedRoutes - The routes that are unused in all data stores and blobs in this Container.
2156
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
2157
+ * tombstones.
2158
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
2159
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
2160
+ * are deleted.
2285
2161
  */
2286
- public deleteUnusedRoutes(unusedRoutes: string[]) {
2287
- /**
2288
- * When running GC in tombstone mode, this is called to tombstone datastore routes that are unused. This
2289
- * enables testing scenarios without actually deleting content. The content acts as if it's deleted to the
2290
- * external user, but the internal runtime does not delete it in summarizes, etc.
2291
- */
2292
- const tombstone = this.mc.config.getBoolean(testTombstoneKey) ?? false;
2293
- // TODO: add blobs
2294
- if (tombstone) {
2295
- // If blob routes are passed in here, tombstone will fail and hit an assert
2296
- this.dataStores.deleteUnusedRoutes(unusedRoutes, tombstone);
2297
- return;
2298
- }
2299
-
2162
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
2300
2163
  const blobManagerUnusedRoutes: string[] = [];
2301
2164
  const dataStoreUnusedRoutes: string[] = [];
2302
2165
  for (const route of unusedRoutes) {
@@ -2307,8 +2170,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2307
2170
  }
2308
2171
  }
2309
2172
 
2310
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2311
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
2173
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
2174
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
2312
2175
  }
2313
2176
 
2314
2177
  /**
@@ -2406,7 +2269,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2406
2269
  },
2407
2270
  );
2408
2271
 
2409
- assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2272
+ assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2410
2273
 
2411
2274
  let latestSnapshotVersionId: string | undefined;
2412
2275
  if (refreshLatestAck) {
@@ -2616,45 +2479,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2616
2479
  }
2617
2480
  }
2618
2481
 
2619
- private processRemoteChunkedMessage(message: ISequencedDocumentMessage) {
2620
- if (message.type !== ContainerMessageType.ChunkedOp) {
2621
- return message;
2622
- }
2623
-
2624
- const clientId = message.clientId;
2625
- const chunkedContent = message.contents as IChunkedOp;
2626
- this.addChunk(clientId, chunkedContent);
2627
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
2628
- const newMessage = { ...message };
2629
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2630
- const serializedContent = this.chunkMap.get(clientId)!.join("");
2631
- newMessage.contents = JSON.parse(serializedContent);
2632
- newMessage.type = chunkedContent.originalType;
2633
- this.clearPartialChunks(clientId);
2634
- return newMessage;
2635
- }
2636
- return message;
2637
- }
2638
-
2639
- private addChunk(clientId: string, chunkedContent: IChunkedOp) {
2640
- let map = this.chunkMap.get(clientId);
2641
- if (map === undefined) {
2642
- map = [];
2643
- this.chunkMap.set(clientId, map);
2644
- }
2645
- assert(chunkedContent.chunkId === map.length + 1,
2646
- 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
2647
- map.push(chunkedContent.contents);
2648
- }
2649
-
2650
- private clearPartialChunks(clientId: string) {
2651
- if (this.chunkMap.has(clientId)) {
2652
- this.chunkMap.delete(clientId);
2653
- }
2654
- }
2655
-
2656
2482
  private hasPendingMessages() {
2657
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
2483
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
2658
2484
  }
2659
2485
 
2660
2486
  private updateDocumentDirtyState(dirty: boolean) {
@@ -2717,7 +2543,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2717
2543
  const serializedContent = JSON.stringify(deserializedContent);
2718
2544
 
2719
2545
  if (this.deltaManager.readOnlyInfo.readonly) {
2720
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
2546
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
2721
2547
  }
2722
2548
 
2723
2549
  const message: BatchMessage = {
@@ -2750,45 +2576,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2750
2576
  // Please note that this does not change file format, so it can be disabled in the future if this
2751
2577
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
2752
2578
  if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
2753
- this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
2754
- if (!this.pendingAttachBatch.push(message)) {
2755
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
2756
- // when queue is not empty.
2757
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
2758
- this.flushBatch(this.pendingAttachBatch.popBatch());
2759
- if (!this.pendingAttachBatch.push(message)) {
2760
- throw new GenericError(
2761
- "BatchTooLarge",
2762
- /* error */ undefined,
2763
- {
2764
- opSize: message.contents.length,
2765
- count: this.pendingAttachBatch.length,
2766
- limit: this.pendingAttachBatch.limit,
2767
- });
2768
- }
2769
- }
2579
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
2580
+ this.outbox.submitAttach(message);
2770
2581
  } else {
2771
- if (!this.pendingBatch.push(message)) {
2772
- throw new GenericError(
2773
- "BatchTooLarge",
2774
- /* error */ undefined,
2775
- {
2776
- opSize: message.contents.length,
2777
- count: this.pendingBatch.length,
2778
- limit: this.pendingBatch.limit,
2779
- });
2780
- }
2781
- if (!this.currentlyBatching()) {
2582
+ this.outbox.submit(message);
2583
+ }
2584
+
2585
+ if (!this.currentlyBatching()) {
2586
+ this.flush();
2587
+ } else if (!this.flushMicroTaskExists) {
2588
+ this.flushMicroTaskExists = true;
2589
+ // Queue a microtask to detect the end of the turn and force a flush.
2590
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
2591
+ Promise.resolve().then(() => {
2592
+ this.flushMicroTaskExists = false;
2782
2593
  this.flush();
2783
- } else if (!this.flushMicroTaskExists) {
2784
- this.flushMicroTaskExists = true;
2785
- // Queue a microtask to detect the end of the turn and force a flush.
2786
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
2787
- Promise.resolve().then(() => {
2788
- this.flushMicroTaskExists = false;
2789
- this.flush();
2790
- });
2791
- }
2594
+ }).catch((error) => { this.closeFn(error as GenericError) });
2792
2595
  }
2793
2596
  } catch (error) {
2794
2597
  this.closeFn(error as GenericError);
@@ -2805,7 +2608,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2805
2608
  assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
2806
2609
 
2807
2610
  // System message should not be sent in the middle of the batch.
2808
- assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
2611
+ assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
2809
2612
 
2810
2613
  // back-compat: ADO #1385: Make this call unconditional in the future
2811
2614
  return this.context.submitSummaryFn !== undefined
@@ -2917,12 +2720,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2917
2720
 
2918
2721
  const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2919
2722
  summaryLogger.sendTelemetryEvent(
2920
- {
2921
- eventName: "LatestSummaryRetrieved",
2922
- ackHandle,
2923
- lastSequenceNumber: latestSnapshotRefSeq,
2924
- targetSequenceNumber: summaryRefSeq,
2925
- });
2723
+ {
2724
+ eventName: "LatestSummaryRetrieved",
2725
+ ackHandle,
2726
+ lastSequenceNumber: latestSnapshotRefSeq,
2727
+ targetSequenceNumber: summaryRefSeq,
2728
+ });
2926
2729
 
2927
2730
  // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
2928
2731
  // wait for the delta manager to catch up before refreshing the latest Summary.
@@ -3013,7 +2816,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3013
2816
  }
3014
2817
  }
3015
2818
 
3016
- public async getSnapshotBlobs(): Promise<void> {
2819
+ private async initializeBaseSnapshotBlobs(): Promise<void> {
3017
2820
  if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) ||
3018
2821
  this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
3019
2822
  return;