@fluidframework/container-runtime 2.0.0-internal.2.1.2 → 2.0.0-internal.2.2.1

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 +22 -19
  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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
3
+ exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.isRuntimeMessage = exports.RuntimeMessage = exports.CompressionAlgorithms = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
4
4
  const container_definitions_1 = require("@fluidframework/container-definitions");
5
5
  const common_utils_1 = require("@fluidframework/common-utils");
6
6
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
@@ -12,14 +12,12 @@ const runtime_definitions_1 = require("@fluidframework/runtime-definitions");
12
12
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
13
13
  const garbage_collector_1 = require("@fluidframework/garbage-collector");
14
14
  const uuid_1 = require("uuid");
15
- const lz4js_1 = require("lz4js");
16
15
  const containerHandleContext_1 = require("./containerHandleContext");
17
16
  const dataStoreRegistry_1 = require("./dataStoreRegistry");
18
17
  const summarizer_1 = require("./summarizer");
19
18
  const summaryManager_1 = require("./summaryManager");
20
19
  const connectionTelemetry_1 = require("./connectionTelemetry");
21
20
  const pendingStateManager_1 = require("./pendingStateManager");
22
- const batchManager_1 = require("./batchManager");
23
21
  const packageVersion_1 = require("./packageVersion");
24
22
  const blobManager_1 = require("./blobManager");
25
23
  const dataStores_1 = require("./dataStores");
@@ -30,10 +28,12 @@ const summarizerClientElection_1 = require("./summarizerClientElection");
30
28
  const throttler_1 = require("./throttler");
31
29
  const runWhileConnectedCoordinator_1 = require("./runWhileConnectedCoordinator");
32
30
  const garbageCollection_1 = require("./garbageCollection");
31
+ const garbageCollectionConstants_1 = require("./garbageCollectionConstants");
33
32
  const dataStore_1 = require("./dataStore");
34
33
  const batchTracker_1 = require("./batchTracker");
35
34
  const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
36
35
  const scheduleManager_1 = require("./scheduleManager");
36
+ const opLifecycle_1 = require("./opLifecycle");
37
37
  var ContainerMessageType;
38
38
  (function (ContainerMessageType) {
39
39
  // An op to be delivered to store
@@ -79,6 +79,13 @@ var RuntimeHeaders;
79
79
  /** True if the request is coming from an IFluidHandle. */
80
80
  RuntimeHeaders["viaHandle"] = "viaHandle";
81
81
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
82
+ /**
83
+ * Available compression algorithms for op compression.
84
+ */
85
+ var CompressionAlgorithms;
86
+ (function (CompressionAlgorithms) {
87
+ CompressionAlgorithms["lz4"] = "lz4";
88
+ })(CompressionAlgorithms = exports.CompressionAlgorithms || (exports.CompressionAlgorithms = {}));
82
89
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
83
90
  const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
84
91
  // The actual limit is 1Mb (socket.io and Kafka limits)
@@ -106,48 +113,6 @@ function isRuntimeMessage(message) {
106
113
  return Object.values(RuntimeMessage).includes(message.type);
107
114
  }
108
115
  exports.isRuntimeMessage = isRuntimeMessage;
109
- /**
110
- * Unpacks runtime messages
111
- *
112
- * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
113
- * @param message - message (as it observed in storage / service)
114
- * @returns unpacked runtime message
115
- *
116
- * @internal
117
- */
118
- function unpackRuntimeMessage(message) {
119
- var _a;
120
- if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
121
- const contents = common_utils_1.IsoBuffer.from(message.contents.contents, "base64");
122
- const decompressedMessage = (0, lz4js_1.decompress)(contents);
123
- const intoString = new TextDecoder().decode(decompressedMessage);
124
- const asObj = JSON.parse(intoString);
125
- message.contents.contents = asObj;
126
- message.metadata.compressed = false;
127
- }
128
- if (message.type === protocol_definitions_1.MessageType.Operation) {
129
- // legacy op format?
130
- if (message.contents.address !== undefined && message.contents.type === undefined) {
131
- message.type = ContainerMessageType.FluidDataStoreOp;
132
- }
133
- else {
134
- // new format
135
- const innerContents = message.contents;
136
- (0, common_utils_1.assert)(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
137
- message.type = innerContents.type;
138
- message.contents = innerContents.contents;
139
- }
140
- return true;
141
- }
142
- else {
143
- // Legacy format, but it's already "unpacked",
144
- // i.e. message.type is actually ContainerMessageType.
145
- // Or it's non-runtime message.
146
- // Nothing to do in such case.
147
- return false;
148
- }
149
- }
150
- exports.unpackRuntimeMessage = unpackRuntimeMessage;
151
116
  /**
152
117
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
153
118
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -174,6 +139,9 @@ exports.getDeviceSpec = getDeviceSpec;
174
139
  * It will define the store level mappings.
175
140
  */
176
141
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
142
+ /**
143
+ * @internal
144
+ */
177
145
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
178
146
  var _a, _b, _c, _d, _e, _f;
179
147
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
@@ -191,7 +159,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
191
159
  this.flushMicroTaskExists = false;
192
160
  this.savedOps = [];
193
161
  this.consecutiveReconnects = 0;
194
- this.compressedOpCount = 0;
195
162
  this._disposed = false;
196
163
  this.emitDirtyDocumentEvent = true;
197
164
  this.defaultTelemetrySignalSampleCount = 100;
@@ -229,9 +196,29 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
229
196
  throw new container_utils_1.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
230
197
  }
231
198
  };
199
+ let loadSummaryNumber;
200
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
201
+ // get the values from the metadata blob.
202
+ if (existing) {
203
+ this.createContainerMetadata = {
204
+ createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
205
+ createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
206
+ };
207
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
208
+ // the count is reset to 0.
209
+ loadSummaryNumber = (_b = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _b !== void 0 ? _b : 0;
210
+ }
211
+ else {
212
+ this.createContainerMetadata = {
213
+ createContainerRuntimeVersion: packageVersion_1.pkgVersion,
214
+ createContainerTimestamp: Date.now(),
215
+ };
216
+ loadSummaryNumber = 0;
217
+ }
218
+ this.nextSummaryNumber = loadSummaryNumber + 1;
232
219
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
233
220
  this._connected = this.context.connected;
234
- this.chunkMap = new Map(chunks);
221
+ this.remoteMessageProcessor = new opLifecycle_1.RemoteMessageProcessor(new opLifecycle_1.OpSplitter(chunks), new opLifecycle_1.OpDecompressor());
235
222
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
236
223
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
237
224
  if (this.summaryConfiguration.state === "enabled") {
@@ -243,18 +230,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
243
230
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
244
231
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
245
232
  this.maxConsecutiveReconnects =
246
- (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
233
+ (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
247
234
  this._flushMode = runtimeOptions.flushMode;
248
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
249
- // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
250
- // latency of processing a batch.
251
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
252
- // payloads. That number represents final (compressed) bits (once compression is implemented).
253
- this.pendingAttachBatch = new batchManager_1.BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
254
- this.pendingBatch = new batchManager_1.BatchManager(runtimeOptions.maxBatchSizeInBytes);
255
235
  const pendingRuntimeState = context.pendingLocalState;
256
- const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
257
- const maxSnapshotCacheDurationMs = (_e = (_d = this._storage) === null || _d === void 0 ? void 0 : _d.policies) === null || _e === void 0 ? void 0 : _e.maximumCacheDurationMs;
236
+ const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
237
+ const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
258
238
  if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
259
239
  // This is a runtime enforcement of what's already explicit in the policy's type itself,
260
240
  // which dictates the value is either undefined or exactly 5 days in ms.
@@ -268,6 +248,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
268
248
  baseLogger: this.mc.logger,
269
249
  existing,
270
250
  metadata,
251
+ createContainerMetadata: this.createContainerMetadata,
271
252
  isSummarizerClient: this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType,
272
253
  getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
273
254
  getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
@@ -302,7 +283,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
302
283
  }
303
284
  }, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
304
285
  this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, () => this.clientId, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
305
- this.deltaSender = this.deltaManager;
306
286
  this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
307
287
  applyStashedOp: this.applyStashedOp.bind(this),
308
288
  clientId: () => this.clientId,
@@ -313,8 +293,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
313
293
  rollback: this.rollback.bind(this),
314
294
  orderSequentially: this.orderSequentially.bind(this),
315
295
  }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
296
+ this.outbox = new opLifecycle_1.Outbox({
297
+ shouldSend: () => this.canSendOps(),
298
+ pendingStateManager: this.pendingStateManager,
299
+ containerContext: this.context,
300
+ compressor: new opLifecycle_1.OpCompressor(this.mc.logger),
301
+ config: {
302
+ compressionOptions: runtimeOptions.compressionOptions,
303
+ maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
304
+ },
305
+ });
316
306
  this.context.quorum.on("removeMember", (clientId) => {
317
- this.clearPartialChunks(clientId);
307
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
318
308
  });
319
309
  this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
320
310
  this.dirtyContainer = this.context.attachState !== container_definitions_1.AttachState.Attached
@@ -382,26 +372,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
382
372
  });
383
373
  // logging hardware telemetry
384
374
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
385
- let loadSummaryNumber;
386
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
387
- // get the values from the metadata blob.
388
- if (existing) {
389
- this.createContainerMetadata = {
390
- createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
391
- createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
392
- };
393
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
394
- // the count is reset to 0.
395
- loadSummaryNumber = (_f = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _f !== void 0 ? _f : 0;
396
- }
397
- else {
398
- this.createContainerMetadata = {
399
- createContainerRuntimeVersion: packageVersion_1.pkgVersion,
400
- createContainerTimestamp: Date.now(),
401
- };
402
- loadSummaryNumber = 0;
403
- }
404
- this.nextSummaryNumber = loadSummaryNumber + 1;
405
375
  this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryNumber: loadSummaryNumber, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
406
376
  (0, connectionTelemetry_1.ReportOpPerfTelemetry)(this.context.clientId, this.deltaManager, this.logger);
407
377
  (0, batchTracker_1.BindBatchTracker)(this, this.logger);
@@ -415,8 +385,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
415
385
  * @param requestHandler - Request handlers for the container runtime
416
386
  * @param runtimeOptions - Additional options to be passed to the runtime
417
387
  * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
388
+ * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
389
+ * allows mixin classes to leverage this method to define their own async initializer.
418
390
  */
419
- static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing) {
391
+ static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing, containerRuntimeCtor = ContainerRuntime) {
420
392
  var _a, _b, _c;
421
393
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
422
394
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -427,7 +399,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
427
399
  runtimeVersion: packageVersion_1.pkgVersion,
428
400
  },
429
401
  });
430
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {}, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
402
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
403
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
404
+ compressionAlgorithm: CompressionAlgorithms.lz4
405
+ }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
431
406
  const pendingRuntimeState = context.pendingLocalState;
432
407
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
433
408
  const storage = !pendingRuntimeState ?
@@ -476,7 +451,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
476
451
  }
477
452
  }
478
453
  }
479
- const runtime = new ContainerRuntime(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
454
+ const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
480
455
  summaryOptions,
481
456
  gcOptions,
482
457
  loadSequenceNumberVerification,
@@ -490,7 +465,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
490
465
  // delete these once runtime has seen them to save space
491
466
  pendingRuntimeState.savedOps = [];
492
467
  }
493
- await runtime.getSnapshotBlobs();
468
+ // Initialize the base state of the runtime before it's returned.
469
+ await runtime.initializeBaseState();
494
470
  return runtime;
495
471
  }
496
472
  get options() {
@@ -539,9 +515,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
539
515
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
540
516
  }
541
517
  get disposed() { return this._disposed; }
542
- get emptyBatch() {
543
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
544
- }
545
518
  get summarizer() {
546
519
  (0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
547
520
  return this._summarizer;
@@ -597,6 +570,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
597
570
  ? this.summaryConfiguration.initialSummarizerDelayMs
598
571
  : 0;
599
572
  }
573
+ /**
574
+ * Initializes the state from the base snapshot this container runtime loaded from.
575
+ */
576
+ async initializeBaseState() {
577
+ await this.initializeBaseSnapshotBlobs();
578
+ await this.garbageCollector.initializeBaseState();
579
+ }
600
580
  dispose(error) {
601
581
  var _a;
602
582
  if (this._disposed) {
@@ -742,8 +722,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
742
722
  addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
743
723
  var _a;
744
724
  this.addMetadataToSummary(summaryTree);
745
- if (this.chunkMap.size > 0) {
746
- const content = JSON.stringify([...this.chunkMap]);
725
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
726
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
747
727
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.chunksBlobName, content);
748
728
  }
749
729
  const dataStoreAliases = this.dataStores.aliases;
@@ -762,7 +742,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
762
742
  }
763
743
  const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
764
744
  if (gcSummary !== undefined) {
765
- (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
745
+ (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollectionConstants_1.gcTreeKey, gcSummary);
766
746
  }
767
747
  }
768
748
  // Track how many times the container tries to reconnect with pending messages.
@@ -907,31 +887,21 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
907
887
  process(messageArg, local) {
908
888
  var _a;
909
889
  this.verifyNotClosed();
910
- // Do shallow copy of message, as methods below will modify it.
911
- // There might be multiple container instances receiving same message
912
- // We do not need to make deep copy, as each layer will just replace message.content itself,
913
- // but would not modify contents details
914
- let message = Object.assign({}, messageArg);
915
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
916
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
917
- // Old ops may contain empty string (I assume noops).
918
- if (typeof message.contents === "string" && message.contents !== "") {
919
- message.contents = JSON.parse(message.contents);
920
- }
921
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
922
- // This format was not shipped to production workflows.
923
- const runtimeMessage = unpackRuntimeMessage(message);
924
890
  if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
925
891
  this.savedOps.push(messageArg);
926
892
  }
893
+ // Whether or not the message is actually a runtime message.
894
+ // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
895
+ // or something different, like a system message.
896
+ const runtimeMessage = messageArg.type === protocol_definitions_1.MessageType.Operation;
897
+ // Do shallow copy of message, as the processing flow will modify it.
898
+ const messageCopy = Object.assign({}, messageArg);
899
+ const message = this.remoteMessageProcessor.process(messageCopy);
927
900
  // Surround the actual processing of the operation with messages to the schedule manager indicating
928
901
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
929
902
  // messages once a batch has been fully processed.
930
903
  this.scheduleManager.beforeOpProcessing(message);
931
904
  try {
932
- // Chunk processing must come first given that we will transform the message to the unchunked version
933
- // once all pieces are available
934
- message = this.processRemoteChunkedMessage(message);
935
905
  let localOpMetadata;
936
906
  if (local && runtimeMessage) {
937
907
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1045,83 +1015,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1045
1015
  */
1046
1016
  flush() {
1047
1017
  (0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1048
- this.flushBatch(this.pendingAttachBatch.popBatch());
1049
- this.flushBatch(this.pendingBatch.popBatch());
1050
- (0, common_utils_1.assert)(this.emptyBatch, 0x3cf /* reentrancy */);
1051
- }
1052
- flushBatch(batch) {
1053
- const length = batch.length;
1054
- if (length > 1) {
1055
- batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
1056
- batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
1057
- // This assert fires for the following reason (there might be more cases like that):
1058
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1059
- // i.e. in the middle of op processing!
1060
- // Sending ops while processing ops is not good idea - it's not defined when
1061
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1062
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1063
- // Tracked via ADO #1834
1064
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1065
- // "Batch should be generated synchronously, without processing ops in the middle!");
1066
- }
1067
- let clientSequenceNumber = -1;
1068
- // Did we disconnect in the middle of turn-based batch?
1069
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1070
- if (this.canSendOps()) {
1071
- if (this.context.submitBatchFn !== undefined) {
1072
- const batchToSend = [];
1073
- for (const message of batch) {
1074
- let contents = message.contents;
1075
- let metadata = message.metadata;
1076
- if (this.runtimeOptions.compressionOptions.minimumSize &&
1077
- this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
1078
- this.compressedOpCount++;
1079
- const copiedMessage = Object.assign({}, message.deserializedContent);
1080
- const compressionStart = Date.now();
1081
- const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
1082
- const compressedContents = (0, lz4js_1.compress)(contentsAsBuffer);
1083
- const compressedContent = common_utils_1.IsoBuffer.from(compressedContents).toString("base64");
1084
- const duration = Date.now() - compressionStart;
1085
- if (this.compressedOpCount % 100) {
1086
- this.mc.logger.sendPerformanceEvent({
1087
- eventName: "compressedOp",
1088
- duration,
1089
- sizeBeforeCompression: message.contents.length,
1090
- sizeAfterCompression: compressedContent.length,
1091
- });
1092
- }
1093
- copiedMessage.contents = compressedContent;
1094
- const stringifiedContents = JSON.stringify(copiedMessage);
1095
- if (stringifiedContents.length < message.contents.length) {
1096
- contents = JSON.stringify(copiedMessage);
1097
- metadata = Object.assign(Object.assign({}, message.metadata), { compressed: true });
1098
- }
1099
- }
1100
- batchToSend.push({ contents, metadata });
1101
- }
1102
- // returns clientSequenceNumber of last message in a batch
1103
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1104
- }
1105
- else {
1106
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1107
- // version that has support for batches (submitBatchFn)
1108
- for (const message of batch) {
1109
- clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
1110
- message.metadata);
1111
- }
1112
- this.deltaSender.flush();
1113
- }
1114
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1115
- clientSequenceNumber -= batch.length - 1;
1116
- (0, common_utils_1.assert)(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1117
- }
1118
- // Let the PendingStateManager know that a message was submitted.
1119
- // In future, need to shift toward keeping batch as a whole!
1120
- for (const message of batch) {
1121
- this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
1122
- clientSequenceNumber++;
1123
- }
1124
- this.pendingStateManager.onFlush();
1018
+ this.outbox.flush();
1019
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1125
1020
  }
1126
1021
  orderSequentially(callback) {
1127
1022
  let checkpoint;
@@ -1129,7 +1024,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1129
1024
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1130
1025
  // 1. It would not help, as we flush attach ops as they become available.
1131
1026
  // 2. There is no way to undo process of data store creation.
1132
- checkpoint = this.pendingBatch.checkpoint();
1027
+ checkpoint = this.outbox.checkpoint().mainBatch;
1133
1028
  }
1134
1029
  try {
1135
1030
  this._orderSequentiallyCalls++;
@@ -1349,33 +1244,27 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1349
1244
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1350
1245
  // always referenced, so the used routes is only self-route (empty string).
1351
1246
  this.summarizerNode.updateUsedRoutes([""]);
1247
+ const blobManagerUsedRoutes = [];
1352
1248
  const dataStoreUsedRoutes = [];
1353
1249
  for (const route of usedRoutes) {
1354
- if (route.split("/")[1] !== blobManager_1.BlobManager.basePath) {
1250
+ if (this.isBlobPath(route)) {
1251
+ blobManagerUsedRoutes.push(route);
1252
+ }
1253
+ else {
1355
1254
  dataStoreUsedRoutes.push(route);
1356
1255
  }
1357
1256
  }
1358
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1257
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
1258
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1359
1259
  }
1360
1260
  /**
1361
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
1362
- * scenarios with accessing deleted content.
1363
- * @param unusedRoutes - The routes that are unused in all data stores and blobs in this Container.
1261
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
1262
+ * tombstones.
1263
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
1264
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
1265
+ * are deleted.
1364
1266
  */
1365
- deleteUnusedRoutes(unusedRoutes) {
1366
- var _a;
1367
- /**
1368
- * When running GC in tombstone mode, this is called to tombstone datastore routes that are unused. This
1369
- * enables testing scenarios without actually deleting content. The content acts as if it's deleted to the
1370
- * external user, but the internal runtime does not delete it in summarizes, etc.
1371
- */
1372
- const tombstone = (_a = this.mc.config.getBoolean(garbageCollection_1.testTombstoneKey)) !== null && _a !== void 0 ? _a : false;
1373
- // TODO: add blobs
1374
- if (tombstone) {
1375
- // If blob routes are passed in here, tombstone will fail and hit an assert
1376
- this.dataStores.deleteUnusedRoutes(unusedRoutes, tombstone);
1377
- return;
1378
- }
1267
+ updateUnusedRoutes(unusedRoutes, tombstone) {
1379
1268
  const blobManagerUnusedRoutes = [];
1380
1269
  const dataStoreUnusedRoutes = [];
1381
1270
  for (const route of unusedRoutes) {
@@ -1386,8 +1275,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1386
1275
  dataStoreUnusedRoutes.push(route);
1387
1276
  }
1388
1277
  }
1389
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1390
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
1278
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
1279
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
1391
1280
  }
1392
1281
  /**
1393
1282
  * Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
@@ -1467,7 +1356,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1467
1356
  const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
1468
1357
  all: { summaryNumber },
1469
1358
  });
1470
- (0, common_utils_1.assert)(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1359
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1471
1360
  let latestSnapshotVersionId;
1472
1361
  if (refreshLatestAck) {
1473
1362
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
@@ -1558,8 +1447,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1558
1447
  const dataStoreTree = summaryTree.tree[runtime_definitions_1.channelsTreeName];
1559
1448
  (0, common_utils_1.assert)(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1560
1449
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
1561
- const gcSummaryTreeStats = summaryTree.tree[garbageCollection_1.gcTreeKey]
1562
- ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollection_1.gcTreeKey])
1450
+ const gcSummaryTreeStats = summaryTree.tree[garbageCollectionConstants_1.gcTreeKey]
1451
+ ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollectionConstants_1.gcTreeKey])
1563
1452
  : undefined;
1564
1453
  const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, summaryNumber }, partialStats);
1565
1454
  const generateSummaryData = {
@@ -1640,40 +1529,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1640
1529
  this.deltaManager.inbound.resume();
1641
1530
  }
1642
1531
  }
1643
- processRemoteChunkedMessage(message) {
1644
- if (message.type !== ContainerMessageType.ChunkedOp) {
1645
- return message;
1646
- }
1647
- const clientId = message.clientId;
1648
- const chunkedContent = message.contents;
1649
- this.addChunk(clientId, chunkedContent);
1650
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
1651
- const newMessage = Object.assign({}, message);
1652
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1653
- const serializedContent = this.chunkMap.get(clientId).join("");
1654
- newMessage.contents = JSON.parse(serializedContent);
1655
- newMessage.type = chunkedContent.originalType;
1656
- this.clearPartialChunks(clientId);
1657
- return newMessage;
1658
- }
1659
- return message;
1660
- }
1661
- addChunk(clientId, chunkedContent) {
1662
- let map = this.chunkMap.get(clientId);
1663
- if (map === undefined) {
1664
- map = [];
1665
- this.chunkMap.set(clientId, map);
1666
- }
1667
- (0, common_utils_1.assert)(chunkedContent.chunkId === map.length + 1, 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
1668
- map.push(chunkedContent.contents);
1669
- }
1670
- clearPartialChunks(clientId) {
1671
- if (this.chunkMap.has(clientId)) {
1672
- this.chunkMap.delete(clientId);
1673
- }
1674
- }
1675
1532
  hasPendingMessages() {
1676
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
1533
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
1677
1534
  }
1678
1535
  updateDocumentDirtyState(dirty) {
1679
1536
  if (this.attachState !== container_definitions_1.AttachState.Attached) {
@@ -1717,7 +1574,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1717
1574
  const deserializedContent = { type, contents };
1718
1575
  const serializedContent = JSON.stringify(deserializedContent);
1719
1576
  if (this.deltaManager.readOnlyInfo.readonly) {
1720
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
1577
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
1721
1578
  }
1722
1579
  const message = {
1723
1580
  contents: serializedContent,
@@ -1748,43 +1605,23 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1748
1605
  // Please note that this does not change file format, so it can be disabled in the future if this
1749
1606
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1750
1607
  if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1751
- this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1752
- if (!this.pendingAttachBatch.push(message)) {
1753
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1754
- // when queue is not empty.
1755
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
1756
- this.flushBatch(this.pendingAttachBatch.popBatch());
1757
- if (!this.pendingAttachBatch.push(message)) {
1758
- throw new container_utils_1.GenericError("BatchTooLarge",
1759
- /* error */ undefined, {
1760
- opSize: message.contents.length,
1761
- count: this.pendingAttachBatch.length,
1762
- limit: this.pendingAttachBatch.limit,
1763
- });
1764
- }
1765
- }
1608
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1609
+ this.outbox.submitAttach(message);
1766
1610
  }
1767
1611
  else {
1768
- if (!this.pendingBatch.push(message)) {
1769
- throw new container_utils_1.GenericError("BatchTooLarge",
1770
- /* error */ undefined, {
1771
- opSize: message.contents.length,
1772
- count: this.pendingBatch.length,
1773
- limit: this.pendingBatch.limit,
1774
- });
1775
- }
1776
- if (!this.currentlyBatching()) {
1612
+ this.outbox.submit(message);
1613
+ }
1614
+ if (!this.currentlyBatching()) {
1615
+ this.flush();
1616
+ }
1617
+ else if (!this.flushMicroTaskExists) {
1618
+ this.flushMicroTaskExists = true;
1619
+ // Queue a microtask to detect the end of the turn and force a flush.
1620
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1621
+ Promise.resolve().then(() => {
1622
+ this.flushMicroTaskExists = false;
1777
1623
  this.flush();
1778
- }
1779
- else if (!this.flushMicroTaskExists) {
1780
- this.flushMicroTaskExists = true;
1781
- // Queue a microtask to detect the end of the turn and force a flush.
1782
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1783
- Promise.resolve().then(() => {
1784
- this.flushMicroTaskExists = false;
1785
- this.flush();
1786
- });
1787
- }
1624
+ }).catch((error) => { this.closeFn(error); });
1788
1625
  }
1789
1626
  }
1790
1627
  catch (error) {
@@ -1799,7 +1636,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1799
1636
  this.verifyNotClosed();
1800
1637
  (0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
1801
1638
  // System message should not be sent in the middle of the batch.
1802
- (0, common_utils_1.assert)(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
1639
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
1803
1640
  // back-compat: ADO #1385: Make this call unconditional in the future
1804
1641
  return this.context.submitSummaryFn !== undefined
1805
1642
  ? this.context.submitSummaryFn(contents)
@@ -1933,7 +1770,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1933
1770
  this.baseSnapshotBlobs = serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1934
1771
  }
1935
1772
  }
1936
- async getSnapshotBlobs() {
1773
+ async initializeBaseSnapshotBlobs() {
1937
1774
  var _a;
1938
1775
  if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1939
1776
  this.attachState !== container_definitions_1.AttachState.Attached || this.context.pendingLocalState) {