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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/blobManager.d.ts +20 -5
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +57 -15
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +16 -33
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +71 -219
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.js +2 -2
  10. package/dist/dataStoreContext.js.map +1 -1
  11. package/dist/dataStores.d.ts.map +1 -1
  12. package/dist/dataStores.js +2 -1
  13. package/dist/dataStores.js.map +1 -1
  14. package/dist/garbageCollection.d.ts +7 -16
  15. package/dist/garbageCollection.d.ts.map +1 -1
  16. package/dist/garbageCollection.js +41 -61
  17. package/dist/garbageCollection.js.map +1 -1
  18. package/dist/garbageCollectionConstants.d.ts +19 -0
  19. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  20. package/dist/garbageCollectionConstants.js +34 -0
  21. package/dist/garbageCollectionConstants.js.map +1 -0
  22. package/dist/gcSweepReadyUsageDetection.js +2 -2
  23. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +7 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/opLifecycle/batchManager.d.ts +30 -0
  29. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  30. package/dist/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  31. package/dist/opLifecycle/batchManager.js.map +1 -0
  32. package/dist/opLifecycle/definitions.d.ts +40 -0
  33. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  34. package/dist/opLifecycle/definitions.js +7 -0
  35. package/dist/opLifecycle/definitions.js.map +1 -0
  36. package/dist/opLifecycle/index.d.ts +12 -0
  37. package/dist/opLifecycle/index.d.ts.map +1 -0
  38. package/dist/opLifecycle/index.js +21 -0
  39. package/dist/opLifecycle/index.js.map +1 -0
  40. package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  41. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  42. package/dist/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  43. package/dist/opLifecycle/opCompressor.js.map +1 -0
  44. package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  45. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  46. package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +5 -5
  47. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  48. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  49. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  50. package/dist/opLifecycle/opSplitter.js +61 -0
  51. package/dist/opLifecycle/opSplitter.js.map +1 -0
  52. package/dist/opLifecycle/outbox.d.ts +47 -0
  53. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  54. package/dist/opLifecycle/outbox.js +153 -0
  55. package/dist/opLifecycle/outbox.js.map +1 -0
  56. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  57. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  58. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  59. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/summaryFormat.js +2 -2
  64. package/dist/summaryFormat.js.map +1 -1
  65. package/lib/blobManager.d.ts +20 -5
  66. package/lib/blobManager.d.ts.map +1 -1
  67. package/lib/blobManager.js +59 -17
  68. package/lib/blobManager.js.map +1 -1
  69. package/lib/containerRuntime.d.ts +16 -33
  70. package/lib/containerRuntime.d.ts.map +1 -1
  71. package/lib/containerRuntime.js +68 -215
  72. package/lib/containerRuntime.js.map +1 -1
  73. package/lib/dataStoreContext.js +1 -1
  74. package/lib/dataStoreContext.js.map +1 -1
  75. package/lib/dataStores.d.ts.map +1 -1
  76. package/lib/dataStores.js +2 -1
  77. package/lib/dataStores.js.map +1 -1
  78. package/lib/garbageCollection.d.ts +7 -16
  79. package/lib/garbageCollection.d.ts.map +1 -1
  80. package/lib/garbageCollection.js +19 -39
  81. package/lib/garbageCollection.js.map +1 -1
  82. package/lib/garbageCollectionConstants.d.ts +19 -0
  83. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  84. package/lib/garbageCollectionConstants.js +31 -0
  85. package/lib/garbageCollectionConstants.js.map +1 -0
  86. package/lib/gcSweepReadyUsageDetection.js +1 -1
  87. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  88. package/lib/index.d.ts +4 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +3 -2
  91. package/lib/index.js.map +1 -1
  92. package/lib/opLifecycle/batchManager.d.ts +30 -0
  93. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  94. package/lib/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  95. package/lib/opLifecycle/batchManager.js.map +1 -0
  96. package/lib/opLifecycle/definitions.d.ts +40 -0
  97. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  98. package/lib/opLifecycle/definitions.js +6 -0
  99. package/lib/opLifecycle/definitions.js.map +1 -0
  100. package/lib/opLifecycle/index.d.ts +12 -0
  101. package/lib/opLifecycle/index.d.ts.map +1 -0
  102. package/lib/opLifecycle/index.js +11 -0
  103. package/lib/opLifecycle/index.js.map +1 -0
  104. package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  105. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  106. package/lib/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  107. package/lib/opLifecycle/opCompressor.js.map +1 -0
  108. package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  109. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  110. package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +4 -4
  111. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  112. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  113. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  114. package/lib/opLifecycle/opSplitter.js +57 -0
  115. package/lib/opLifecycle/opSplitter.js.map +1 -0
  116. package/lib/opLifecycle/outbox.d.ts +47 -0
  117. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  118. package/lib/opLifecycle/outbox.js +149 -0
  119. package/lib/opLifecycle/outbox.js.map +1 -0
  120. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  121. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  122. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  123. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  124. package/lib/packageVersion.d.ts +1 -1
  125. package/lib/packageVersion.js +1 -1
  126. package/lib/packageVersion.js.map +1 -1
  127. package/lib/summaryFormat.js +1 -1
  128. package/lib/summaryFormat.js.map +1 -1
  129. package/package.json +21 -34
  130. package/src/blobManager.ts +74 -19
  131. package/src/containerRuntime.ts +91 -278
  132. package/src/dataStoreContext.ts +1 -1
  133. package/src/dataStores.ts +2 -1
  134. package/src/garbageCollection.ts +33 -43
  135. package/src/garbageCollectionConstants.ts +35 -0
  136. package/src/gcSweepReadyUsageDetection.ts +1 -1
  137. package/src/index.ts +5 -4
  138. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +30 -33
  139. package/src/opLifecycle/definitions.ts +44 -0
  140. package/src/opLifecycle/index.ts +17 -0
  141. package/src/{opCompressor.ts → opLifecycle/opCompressor.ts} +21 -16
  142. package/src/{opDecompressor.ts → opLifecycle/opDecompressor.ts} +8 -6
  143. package/src/opLifecycle/opSplitter.ts +78 -0
  144. package/src/opLifecycle/outbox.ts +204 -0
  145. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  146. package/src/packageVersion.ts +1 -1
  147. package/src/summaryFormat.ts +1 -1
  148. package/dist/batchManager.d.ts +0 -42
  149. package/dist/batchManager.d.ts.map +0 -1
  150. package/dist/batchManager.js.map +0 -1
  151. package/dist/opCompressor.d.ts.map +0 -1
  152. package/dist/opCompressor.js.map +0 -1
  153. package/dist/opDecompressor.d.ts.map +0 -1
  154. package/dist/opDecompressor.js.map +0 -1
  155. package/lib/batchManager.d.ts +0 -42
  156. package/lib/batchManager.d.ts.map +0 -1
  157. package/lib/batchManager.js.map +0 -1
  158. package/lib/opCompressor.d.ts.map +0 -1
  159. package/lib/opCompressor.js.map +0 -1
  160. package/lib/opDecompressor.d.ts.map +0 -1
  161. package/lib/opDecompressor.js.map +0 -1
@@ -15,7 +15,6 @@ import { Summarizer } from "./summarizer";
15
15
  import { SummaryManager } from "./summaryManager";
16
16
  import { ReportOpPerfTelemetry, } from "./connectionTelemetry";
17
17
  import { PendingStateManager, } from "./pendingStateManager";
18
- import { BatchManager } from "./batchManager";
19
18
  import { pkgVersion } from "./packageVersion";
20
19
  import { BlobManager } from "./blobManager";
21
20
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -25,12 +24,13 @@ import { OrderedClientCollection, OrderedClientElection } from "./orderedClientE
25
24
  import { SummarizerClientElection, summarizerClientType } from "./summarizerClientElection";
26
25
  import { formExponentialFn, Throttler } from "./throttler";
27
26
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
28
- import { GarbageCollector, GCNodeType, gcTreeKey, } from "./garbageCollection";
27
+ import { GarbageCollector, GCNodeType, } from "./garbageCollection";
28
+ import { gcTreeKey, } from "./garbageCollectionConstants";
29
29
  import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
30
30
  import { BindBatchTracker } from "./batchTracker";
31
31
  import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
32
32
  import { ScheduleManager } from "./scheduleManager";
33
- import { OpDecompressor } from "./opDecompressor";
33
+ import { OpCompressor, OpDecompressor, Outbox, OpSplitter, RemoteMessageProcessor, } from "./opLifecycle";
34
34
  export var ContainerMessageType;
35
35
  (function (ContainerMessageType) {
36
36
  // An op to be delivered to store
@@ -109,37 +109,6 @@ export var RuntimeMessage;
109
109
  export function isRuntimeMessage(message) {
110
110
  return Object.values(RuntimeMessage).includes(message.type);
111
111
  }
112
- /**
113
- * Unpacks runtime messages
114
- *
115
- * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
116
- * @param message - message (as it observed in storage / service)
117
- * @returns unpacked runtime message
118
- *
119
- * @internal
120
- */
121
- export function unpackRuntimeMessage(message) {
122
- if (message.type === MessageType.Operation) {
123
- // legacy op format?
124
- if (message.contents.address !== undefined && message.contents.type === undefined) {
125
- message.type = ContainerMessageType.FluidDataStoreOp;
126
- }
127
- else {
128
- // new format
129
- const innerContents = message.contents;
130
- message.type = innerContents.type;
131
- message.contents = innerContents.contents;
132
- }
133
- return true;
134
- }
135
- else {
136
- // Legacy format, but it's already "unpacked",
137
- // i.e. message.type is actually ContainerMessageType.
138
- // Or it's non-runtime message.
139
- // Nothing to do in such case.
140
- return false;
141
- }
142
- }
143
112
  /**
144
113
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
145
114
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -165,6 +134,9 @@ export function getDeviceSpec() {
165
134
  * It will define the store level mappings.
166
135
  */
167
136
  export class ContainerRuntime extends TypedEventEmitter {
137
+ /**
138
+ * @internal
139
+ */
168
140
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
169
141
  var _a, _b, _c, _d, _e, _f;
170
142
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
@@ -177,7 +149,6 @@ export class ContainerRuntime extends TypedEventEmitter {
177
149
  this._storage = _storage;
178
150
  this.requestHandler = requestHandler;
179
151
  this.summaryConfiguration = summaryConfiguration;
180
- this.opDecompressor = new OpDecompressor();
181
152
  this.defaultMaxConsecutiveReconnects = 7;
182
153
  this._orderSequentiallyCalls = 0;
183
154
  this.flushMicroTaskExists = false;
@@ -242,7 +213,7 @@ export class ContainerRuntime extends TypedEventEmitter {
242
213
  this.nextSummaryNumber = loadSummaryNumber + 1;
243
214
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
244
215
  this._connected = this.context.connected;
245
- this.chunkMap = new Map(chunks);
216
+ this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
246
217
  this.handleContext = new ContainerFluidHandleContext("", this);
247
218
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
248
219
  if (this.summaryConfiguration.state === "enabled") {
@@ -256,20 +227,6 @@ export class ContainerRuntime extends TypedEventEmitter {
256
227
  this.maxConsecutiveReconnects =
257
228
  (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
258
229
  this._flushMode = runtimeOptions.flushMode;
259
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
260
- // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
261
- // latency of processing a batch.
262
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
263
- // payloads. That number represents final (compressed) bits (once compression is implemented).
264
- this.pendingAttachBatch = new BatchManager(this.mc.logger, {
265
- hardLimit: runtimeOptions.maxBatchSizeInBytes,
266
- softLimit: 64 * 1024,
267
- compressionOptions: runtimeOptions.compressionOptions
268
- });
269
- this.pendingBatch = new BatchManager(this.mc.logger, {
270
- hardLimit: runtimeOptions.maxBatchSizeInBytes,
271
- compressionOptions: runtimeOptions.compressionOptions
272
- });
273
230
  const pendingRuntimeState = context.pendingLocalState;
274
231
  const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
275
232
  const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
@@ -321,7 +278,6 @@ export class ContainerRuntime extends TypedEventEmitter {
321
278
  }
322
279
  }, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
323
280
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, () => this.clientId, ChildLogger.create(this.logger, "ScheduleManager"));
324
- this.deltaSender = this.deltaManager;
325
281
  this.pendingStateManager = new PendingStateManager({
326
282
  applyStashedOp: this.applyStashedOp.bind(this),
327
283
  clientId: () => this.clientId,
@@ -332,8 +288,18 @@ export class ContainerRuntime extends TypedEventEmitter {
332
288
  rollback: this.rollback.bind(this),
333
289
  orderSequentially: this.orderSequentially.bind(this),
334
290
  }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
291
+ this.outbox = new Outbox({
292
+ shouldSend: () => this.canSendOps(),
293
+ pendingStateManager: this.pendingStateManager,
294
+ containerContext: this.context,
295
+ compressor: new OpCompressor(this.mc.logger),
296
+ config: {
297
+ compressionOptions: runtimeOptions.compressionOptions,
298
+ maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
299
+ },
300
+ });
335
301
  this.context.quorum.on("removeMember", (clientId) => {
336
- this.clearPartialChunks(clientId);
302
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
337
303
  });
338
304
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
339
305
  this.dirtyContainer = this.context.attachState !== AttachState.Attached
@@ -414,8 +380,10 @@ export class ContainerRuntime extends TypedEventEmitter {
414
380
  * @param requestHandler - Request handlers for the container runtime
415
381
  * @param runtimeOptions - Additional options to be passed to the runtime
416
382
  * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
383
+ * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
384
+ * allows mixin classes to leverage this method to define their own async initializer.
417
385
  */
418
- static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing) {
386
+ static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing, containerRuntimeCtor = ContainerRuntime) {
419
387
  var _a, _b, _c;
420
388
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
421
389
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -426,8 +394,10 @@ export class ContainerRuntime extends TypedEventEmitter {
426
394
  runtimeVersion: pkgVersion,
427
395
  },
428
396
  });
429
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = { minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
430
- compressionAlgorithm: CompressionAlgorithms.lz4 }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
397
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
398
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
399
+ compressionAlgorithm: CompressionAlgorithms.lz4
400
+ }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
431
401
  const pendingRuntimeState = context.pendingLocalState;
432
402
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
433
403
  const storage = !pendingRuntimeState ?
@@ -476,7 +446,7 @@ export class ContainerRuntime extends TypedEventEmitter {
476
446
  }
477
447
  }
478
448
  }
479
- const runtime = new ContainerRuntime(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
449
+ const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
480
450
  summaryOptions,
481
451
  gcOptions,
482
452
  loadSequenceNumberVerification,
@@ -540,9 +510,6 @@ export class ContainerRuntime extends TypedEventEmitter {
540
510
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
541
511
  }
542
512
  get disposed() { return this._disposed; }
543
- get emptyBatch() {
544
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
545
- }
546
513
  get summarizer() {
547
514
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
548
515
  return this._summarizer;
@@ -750,8 +717,8 @@ export class ContainerRuntime extends TypedEventEmitter {
750
717
  addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
751
718
  var _a;
752
719
  this.addMetadataToSummary(summaryTree);
753
- if (this.chunkMap.size > 0) {
754
- const content = JSON.stringify([...this.chunkMap]);
720
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
721
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
755
722
  addBlobToSummary(summaryTree, chunksBlobName, content);
756
723
  }
757
724
  const dataStoreAliases = this.dataStores.aliases;
@@ -915,32 +882,21 @@ export class ContainerRuntime extends TypedEventEmitter {
915
882
  process(messageArg, local) {
916
883
  var _a;
917
884
  this.verifyNotClosed();
918
- // Do shallow copy of message, as methods below will modify it.
919
- // There might be multiple container instances receiving same message
920
- // We do not need to make deep copy, as each layer will just replace message.content itself,
921
- // but would not modify contents details
922
- let message = Object.assign({}, messageArg);
923
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
924
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
925
- // Old ops may contain empty string (I assume noops).
926
- if (typeof message.contents === "string" && message.contents !== "") {
927
- message.contents = JSON.parse(message.contents);
928
- }
929
- message = this.opDecompressor.processMessage(message);
930
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
931
- // This format was not shipped to production workflows.
932
- const runtimeMessage = unpackRuntimeMessage(message);
933
885
  if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
934
886
  this.savedOps.push(messageArg);
935
887
  }
888
+ // Whether or not the message is actually a runtime message.
889
+ // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
890
+ // or something different, like a system message.
891
+ const runtimeMessage = messageArg.type === MessageType.Operation;
892
+ // Do shallow copy of message, as the processing flow will modify it.
893
+ const messageCopy = Object.assign({}, messageArg);
894
+ const message = this.remoteMessageProcessor.process(messageCopy);
936
895
  // Surround the actual processing of the operation with messages to the schedule manager indicating
937
896
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
938
897
  // messages once a batch has been fully processed.
939
898
  this.scheduleManager.beforeOpProcessing(message);
940
899
  try {
941
- // Chunk processing must come first given that we will transform the message to the unchunked version
942
- // once all pieces are available
943
- message = this.processRemoteChunkedMessage(message);
944
900
  let localOpMetadata;
945
901
  if (local && runtimeMessage) {
946
902
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1054,74 +1010,21 @@ export class ContainerRuntime extends TypedEventEmitter {
1054
1010
  */
1055
1011
  flush() {
1056
1012
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1057
- this.flushBatch(this.pendingAttachBatch.popBatch());
1058
- this.flushBatch(this.pendingBatch.popBatch());
1059
- assert(this.emptyBatch, 0x3cf /* reentrancy */);
1060
- }
1061
- flushBatch(batch) {
1062
- var _a;
1063
- const length = batch.length;
1064
- if (length > 1) {
1065
- batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
1066
- batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
1067
- // This assert fires for the following reason (there might be more cases like that):
1068
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1069
- // i.e. in the middle of op processing!
1070
- // Sending ops while processing ops is not good idea - it's not defined when
1071
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1072
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1073
- // Tracked via ADO #1834
1074
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1075
- // "Batch should be generated synchronously, without processing ops in the middle!");
1076
- }
1077
- let clientSequenceNumber = -1;
1078
- // Did we disconnect in the middle of turn-based batch?
1079
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1080
- if (this.canSendOps()) {
1081
- if (this.context.submitBatchFn !== undefined) {
1082
- const batchToSend = [];
1083
- for (const message of batch) {
1084
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1085
- }
1086
- // returns clientSequenceNumber of last message in a batch
1087
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1088
- }
1089
- else {
1090
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1091
- // version that has support for batches (submitBatchFn)
1092
- for (const message of batch) {
1093
- // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
1094
- if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
1095
- delete message.metadata.compressed;
1096
- }
1097
- clientSequenceNumber = this.context.submitFn(MessageType.Operation, message.deserializedContent, true, // batch
1098
- message.metadata);
1099
- }
1100
- this.deltaSender.flush();
1101
- }
1102
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1103
- clientSequenceNumber -= batch.length - 1;
1104
- assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1105
- }
1106
- // Let the PendingStateManager know that a message was submitted.
1107
- // In future, need to shift toward keeping batch as a whole!
1108
- for (const message of batch) {
1109
- this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
1110
- clientSequenceNumber++;
1111
- }
1112
- this.pendingStateManager.onFlush();
1013
+ this.outbox.flush();
1014
+ assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1113
1015
  }
1114
1016
  orderSequentially(callback) {
1115
1017
  let checkpoint;
1018
+ let result;
1116
1019
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1117
1020
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1118
1021
  // 1. It would not help, as we flush attach ops as they become available.
1119
1022
  // 2. There is no way to undo process of data store creation.
1120
- checkpoint = this.pendingBatch.checkpoint();
1023
+ checkpoint = this.outbox.checkpoint().mainBatch;
1121
1024
  }
1122
1025
  try {
1123
1026
  this._orderSequentiallyCalls++;
1124
- callback();
1027
+ result = callback();
1125
1028
  }
1126
1029
  catch (error) {
1127
1030
  if (checkpoint) {
@@ -1149,6 +1052,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1149
1052
  if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1150
1053
  this.flush();
1151
1054
  }
1055
+ return result;
1152
1056
  }
1153
1057
  async createDataStore(pkg) {
1154
1058
  const internalId = uuid();
@@ -1337,18 +1241,23 @@ export class ContainerRuntime extends TypedEventEmitter {
1337
1241
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1338
1242
  // always referenced, so the used routes is only self-route (empty string).
1339
1243
  this.summarizerNode.updateUsedRoutes([""]);
1244
+ const blobManagerUsedRoutes = [];
1340
1245
  const dataStoreUsedRoutes = [];
1341
1246
  for (const route of usedRoutes) {
1342
- if (route.split("/")[1] !== BlobManager.basePath) {
1247
+ if (this.isBlobPath(route)) {
1248
+ blobManagerUsedRoutes.push(route);
1249
+ }
1250
+ else {
1343
1251
  dataStoreUsedRoutes.push(route);
1344
1252
  }
1345
1253
  }
1346
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1254
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
1255
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1347
1256
  }
1348
1257
  /**
1349
1258
  * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
1350
1259
  * tombstones.
1351
- * @param unusedRoutes - The routes that are unused in all data stores in this Container.
1260
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
1352
1261
  * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
1353
1262
  * are deleted.
1354
1263
  */
@@ -1363,10 +1272,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1363
1272
  dataStoreUnusedRoutes.push(route);
1364
1273
  }
1365
1274
  }
1366
- // Todo: Add tombstone for attachment blobs. For now, we ignore attachment blobs that should be tombstoned.
1367
- if (!tombstone) {
1368
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1369
- }
1275
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
1370
1276
  this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
1371
1277
  }
1372
1278
  /**
@@ -1447,7 +1353,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1447
1353
  const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
1448
1354
  all: { summaryNumber },
1449
1355
  });
1450
- assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1356
+ assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1451
1357
  let latestSnapshotVersionId;
1452
1358
  if (refreshLatestAck) {
1453
1359
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
@@ -1620,40 +1526,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1620
1526
  this.deltaManager.inbound.resume();
1621
1527
  }
1622
1528
  }
1623
- processRemoteChunkedMessage(message) {
1624
- if (message.type !== ContainerMessageType.ChunkedOp) {
1625
- return message;
1626
- }
1627
- const clientId = message.clientId;
1628
- const chunkedContent = message.contents;
1629
- this.addChunk(clientId, chunkedContent);
1630
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
1631
- const newMessage = Object.assign({}, message);
1632
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1633
- const serializedContent = this.chunkMap.get(clientId).join("");
1634
- newMessage.contents = JSON.parse(serializedContent);
1635
- newMessage.type = chunkedContent.originalType;
1636
- this.clearPartialChunks(clientId);
1637
- return newMessage;
1638
- }
1639
- return message;
1640
- }
1641
- addChunk(clientId, chunkedContent) {
1642
- let map = this.chunkMap.get(clientId);
1643
- if (map === undefined) {
1644
- map = [];
1645
- this.chunkMap.set(clientId, map);
1646
- }
1647
- assert(chunkedContent.chunkId === map.length + 1, 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
1648
- map.push(chunkedContent.contents);
1649
- }
1650
- clearPartialChunks(clientId) {
1651
- if (this.chunkMap.has(clientId)) {
1652
- this.chunkMap.delete(clientId);
1653
- }
1654
- }
1655
1529
  hasPendingMessages() {
1656
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
1530
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
1657
1531
  }
1658
1532
  updateDocumentDirtyState(dirty) {
1659
1533
  if (this.attachState !== AttachState.Attached) {
@@ -1691,14 +1565,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1691
1565
  return this.blobManager.createBlob(blob);
1692
1566
  }
1693
1567
  submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
1694
- var _a, _b, _c, _d;
1695
1568
  this.verifyNotClosed();
1696
1569
  // There should be no ops in detached container state!
1697
1570
  assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1698
1571
  const deserializedContent = { type, contents };
1699
1572
  const serializedContent = JSON.stringify(deserializedContent);
1700
1573
  if (this.deltaManager.readOnlyInfo.readonly) {
1701
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
1574
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
1702
1575
  }
1703
1576
  const message = {
1704
1577
  contents: serializedContent,
@@ -1729,43 +1602,23 @@ export class ContainerRuntime extends TypedEventEmitter {
1729
1602
  // Please note that this does not change file format, so it can be disabled in the future if this
1730
1603
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1731
1604
  if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1732
- this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1733
- if (!this.pendingAttachBatch.push(message)) {
1734
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1735
- // when queue is not empty.
1736
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
1737
- this.flushBatch(this.pendingAttachBatch.popBatch());
1738
- if (!this.pendingAttachBatch.push(message)) {
1739
- throw new GenericError("BatchTooLarge",
1740
- /* error */ undefined, {
1741
- opSize: (_b = ((_a = message.contents) === null || _a === void 0 ? void 0 : _a.length)) !== null && _b !== void 0 ? _b : 0,
1742
- count: this.pendingAttachBatch.length,
1743
- limit: this.pendingAttachBatch.options.hardLimit,
1744
- });
1745
- }
1746
- }
1605
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1606
+ this.outbox.submitAttach(message);
1747
1607
  }
1748
1608
  else {
1749
- if (!this.pendingBatch.push(message)) {
1750
- throw new GenericError("BatchTooLarge",
1751
- /* error */ undefined, {
1752
- opSize: (_d = ((_c = message.contents) === null || _c === void 0 ? void 0 : _c.length)) !== null && _d !== void 0 ? _d : 0,
1753
- count: this.pendingBatch.length,
1754
- limit: this.pendingBatch.options.hardLimit,
1755
- });
1756
- }
1757
- if (!this.currentlyBatching()) {
1609
+ this.outbox.submit(message);
1610
+ }
1611
+ if (!this.currentlyBatching()) {
1612
+ this.flush();
1613
+ }
1614
+ else if (!this.flushMicroTaskExists) {
1615
+ this.flushMicroTaskExists = true;
1616
+ // Queue a microtask to detect the end of the turn and force a flush.
1617
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1618
+ Promise.resolve().then(() => {
1619
+ this.flushMicroTaskExists = false;
1758
1620
  this.flush();
1759
- }
1760
- else if (!this.flushMicroTaskExists) {
1761
- this.flushMicroTaskExists = true;
1762
- // Queue a microtask to detect the end of the turn and force a flush.
1763
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1764
- Promise.resolve().then(() => {
1765
- this.flushMicroTaskExists = false;
1766
- this.flush();
1767
- });
1768
- }
1621
+ }).catch((error) => { this.closeFn(error); });
1769
1622
  }
1770
1623
  }
1771
1624
  catch (error) {
@@ -1780,7 +1633,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1780
1633
  this.verifyNotClosed();
1781
1634
  assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
1782
1635
  // System message should not be sent in the middle of the batch.
1783
- assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
1636
+ assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
1784
1637
  // back-compat: ADO #1385: Make this call unconditional in the future
1785
1638
  return this.context.submitSummaryFn !== undefined
1786
1639
  ? this.context.submitSummaryFn(contents)