@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 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 (239) 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 +88 -51
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +205 -300
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +51 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +40 -32
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +227 -161
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/garbageCollectionConstants.d.ts +19 -0
  28. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  29. package/dist/garbageCollectionConstants.js +34 -0
  30. package/dist/garbageCollectionConstants.js.map +1 -0
  31. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  32. package/dist/gcSweepReadyUsageDetection.js +5 -14
  33. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/opLifecycle/batchManager.d.ts +30 -0
  39. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  40. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  41. package/dist/opLifecycle/batchManager.js.map +1 -0
  42. package/dist/opLifecycle/definitions.d.ts +40 -0
  43. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  44. package/dist/opLifecycle/definitions.js +7 -0
  45. package/dist/opLifecycle/definitions.js.map +1 -0
  46. package/dist/opLifecycle/index.d.ts +12 -0
  47. package/dist/opLifecycle/index.d.ts.map +1 -0
  48. package/dist/opLifecycle/index.js +21 -0
  49. package/dist/opLifecycle/index.js.map +1 -0
  50. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  52. package/dist/opLifecycle/opCompressor.js +53 -0
  53. package/dist/opLifecycle/opCompressor.js.map +1 -0
  54. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  55. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  56. package/dist/opLifecycle/opDecompressor.js +72 -0
  57. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  58. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  60. package/dist/opLifecycle/opSplitter.js +61 -0
  61. package/dist/opLifecycle/opSplitter.js.map +1 -0
  62. package/dist/opLifecycle/outbox.d.ts +47 -0
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  64. package/dist/opLifecycle/outbox.js +153 -0
  65. package/dist/opLifecycle/outbox.js.map +1 -0
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  68. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  69. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/pendingStateManager.d.ts +6 -26
  74. package/dist/pendingStateManager.d.ts.map +1 -1
  75. package/dist/pendingStateManager.js +42 -62
  76. package/dist/pendingStateManager.js.map +1 -1
  77. package/dist/runningSummarizer.d.ts +3 -2
  78. package/dist/runningSummarizer.d.ts.map +1 -1
  79. package/dist/runningSummarizer.js +10 -3
  80. package/dist/runningSummarizer.js.map +1 -1
  81. package/dist/scheduleManager.js.map +1 -1
  82. package/dist/summarizer.js +7 -2
  83. package/dist/summarizer.js.map +1 -1
  84. package/dist/summarizerClientElection.js +1 -1
  85. package/dist/summarizerClientElection.js.map +1 -1
  86. package/dist/summarizerHeuristics.d.ts.map +1 -1
  87. package/dist/summarizerHeuristics.js +0 -3
  88. package/dist/summarizerHeuristics.js.map +1 -1
  89. package/dist/summarizerTypes.d.ts +19 -2
  90. package/dist/summarizerTypes.d.ts.map +1 -1
  91. package/dist/summarizerTypes.js.map +1 -1
  92. package/dist/summaryFormat.d.ts +4 -2
  93. package/dist/summaryFormat.d.ts.map +1 -1
  94. package/dist/summaryFormat.js +2 -2
  95. package/dist/summaryFormat.js.map +1 -1
  96. package/dist/summaryGenerator.d.ts.map +1 -1
  97. package/dist/summaryGenerator.js +3 -2
  98. package/dist/summaryGenerator.js.map +1 -1
  99. package/dist/summaryManager.d.ts.map +1 -1
  100. package/dist/summaryManager.js +10 -6
  101. package/dist/summaryManager.js.map +1 -1
  102. package/garbageCollection.md +27 -22
  103. package/lib/blobManager.d.ts +20 -5
  104. package/lib/blobManager.d.ts.map +1 -1
  105. package/lib/blobManager.js +59 -17
  106. package/lib/blobManager.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +88 -51
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +203 -297
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts.map +1 -1
  112. package/lib/dataStore.js +6 -0
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts +14 -21
  115. package/lib/dataStoreContext.d.ts.map +1 -1
  116. package/lib/dataStoreContext.js +75 -61
  117. package/lib/dataStoreContext.js.map +1 -1
  118. package/lib/dataStoreContexts.js +1 -1
  119. package/lib/dataStoreContexts.js.map +1 -1
  120. package/lib/dataStores.d.ts +11 -10
  121. package/lib/dataStores.d.ts.map +1 -1
  122. package/lib/dataStores.js +53 -22
  123. package/lib/dataStores.js.map +1 -1
  124. package/lib/garbageCollection.d.ts +40 -32
  125. package/lib/garbageCollection.d.ts.map +1 -1
  126. package/lib/garbageCollection.js +220 -154
  127. package/lib/garbageCollection.js.map +1 -1
  128. package/lib/garbageCollectionConstants.d.ts +19 -0
  129. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  130. package/lib/garbageCollectionConstants.js +31 -0
  131. package/lib/garbageCollectionConstants.js.map +1 -0
  132. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  133. package/lib/gcSweepReadyUsageDetection.js +4 -13
  134. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  135. package/lib/index.d.ts +6 -6
  136. package/lib/index.d.ts.map +1 -1
  137. package/lib/index.js +3 -4
  138. package/lib/index.js.map +1 -1
  139. package/lib/opLifecycle/batchManager.d.ts +30 -0
  140. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  141. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  142. package/lib/opLifecycle/batchManager.js.map +1 -0
  143. package/lib/opLifecycle/definitions.d.ts +40 -0
  144. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  145. package/lib/opLifecycle/definitions.js +6 -0
  146. package/lib/opLifecycle/definitions.js.map +1 -0
  147. package/lib/opLifecycle/index.d.ts +12 -0
  148. package/lib/opLifecycle/index.d.ts.map +1 -0
  149. package/lib/opLifecycle/index.js +11 -0
  150. package/lib/opLifecycle/index.js.map +1 -0
  151. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  152. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  153. package/lib/opLifecycle/opCompressor.js +49 -0
  154. package/lib/opLifecycle/opCompressor.js.map +1 -0
  155. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  156. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  157. package/lib/opLifecycle/opDecompressor.js +68 -0
  158. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  159. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  160. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  161. package/lib/opLifecycle/opSplitter.js +57 -0
  162. package/lib/opLifecycle/opSplitter.js.map +1 -0
  163. package/lib/opLifecycle/outbox.d.ts +47 -0
  164. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  165. package/lib/opLifecycle/outbox.js +149 -0
  166. package/lib/opLifecycle/outbox.js.map +1 -0
  167. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  168. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  169. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  170. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  171. package/lib/packageVersion.d.ts +1 -1
  172. package/lib/packageVersion.js +1 -1
  173. package/lib/packageVersion.js.map +1 -1
  174. package/lib/pendingStateManager.d.ts +6 -26
  175. package/lib/pendingStateManager.d.ts.map +1 -1
  176. package/lib/pendingStateManager.js +42 -62
  177. package/lib/pendingStateManager.js.map +1 -1
  178. package/lib/runningSummarizer.d.ts +3 -2
  179. package/lib/runningSummarizer.d.ts.map +1 -1
  180. package/lib/runningSummarizer.js +10 -3
  181. package/lib/runningSummarizer.js.map +1 -1
  182. package/lib/scheduleManager.js.map +1 -1
  183. package/lib/summarizer.js +7 -2
  184. package/lib/summarizer.js.map +1 -1
  185. package/lib/summarizerClientElection.js +1 -1
  186. package/lib/summarizerClientElection.js.map +1 -1
  187. package/lib/summarizerHeuristics.d.ts.map +1 -1
  188. package/lib/summarizerHeuristics.js +0 -3
  189. package/lib/summarizerHeuristics.js.map +1 -1
  190. package/lib/summarizerTypes.d.ts +19 -2
  191. package/lib/summarizerTypes.d.ts.map +1 -1
  192. package/lib/summarizerTypes.js.map +1 -1
  193. package/lib/summaryFormat.d.ts +4 -2
  194. package/lib/summaryFormat.d.ts.map +1 -1
  195. package/lib/summaryFormat.js +1 -1
  196. package/lib/summaryFormat.js.map +1 -1
  197. package/lib/summaryGenerator.d.ts.map +1 -1
  198. package/lib/summaryGenerator.js +3 -2
  199. package/lib/summaryGenerator.js.map +1 -1
  200. package/lib/summaryManager.d.ts.map +1 -1
  201. package/lib/summaryManager.js +10 -6
  202. package/lib/summaryManager.js.map +1 -1
  203. package/package.json +32 -71
  204. package/prettier.config.cjs +8 -0
  205. package/src/blobManager.ts +74 -19
  206. package/src/containerRuntime.ts +286 -369
  207. package/src/dataStore.ts +13 -1
  208. package/src/dataStoreContext.ts +100 -76
  209. package/src/dataStoreContexts.ts +1 -1
  210. package/src/dataStores.ts +61 -22
  211. package/src/garbageCollection.ts +282 -163
  212. package/src/garbageCollectionConstants.ts +35 -0
  213. package/src/gcSweepReadyUsageDetection.ts +3 -11
  214. package/src/index.ts +9 -8
  215. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +42 -28
  216. package/src/opLifecycle/definitions.ts +44 -0
  217. package/src/opLifecycle/index.ts +17 -0
  218. package/src/opLifecycle/opCompressor.ts +64 -0
  219. package/src/opLifecycle/opDecompressor.ts +84 -0
  220. package/src/opLifecycle/opSplitter.ts +78 -0
  221. package/src/opLifecycle/outbox.ts +204 -0
  222. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  223. package/src/packageVersion.ts +1 -1
  224. package/src/pendingStateManager.ts +57 -96
  225. package/src/runningSummarizer.ts +11 -3
  226. package/src/scheduleManager.ts +1 -0
  227. package/src/summarizer.ts +6 -6
  228. package/src/summarizerClientElection.ts +1 -1
  229. package/src/summarizerHeuristics.ts +0 -3
  230. package/src/summarizerTypes.ts +20 -7
  231. package/src/summaryFormat.ts +5 -3
  232. package/src/summaryGenerator.ts +3 -2
  233. package/src/summaryManager.ts +18 -7
  234. package/dist/batchManager.d.ts +0 -37
  235. package/dist/batchManager.d.ts.map +0 -1
  236. package/dist/batchManager.js.map +0 -1
  237. package/lib/batchManager.d.ts +0 -37
  238. package/lib/batchManager.d.ts.map +0 -1
  239. 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,
@@ -119,7 +117,6 @@ import {
119
117
  IPendingLocalState,
120
118
  PendingStateManager,
121
119
  } from "./pendingStateManager";
122
- import { BatchManager, BatchMessage } from "./batchManager";
123
120
  import { pkgVersion } from "./packageVersion";
124
121
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
125
122
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -147,17 +144,20 @@ import {
147
144
  ISummarizerInternalsProvider,
148
145
  ISummarizerOptions,
149
146
  ISummarizerRuntime,
147
+ IRefreshSummaryAckOptions,
150
148
  } from "./summarizerTypes";
151
149
  import { formExponentialFn, Throttler } from "./throttler";
152
150
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
153
151
  import {
154
152
  GarbageCollector,
155
153
  GCNodeType,
156
- gcTreeKey,
157
154
  IGarbageCollectionRuntime,
158
155
  IGarbageCollector,
159
156
  IGCStats,
160
157
  } from "./garbageCollection";
158
+ import {
159
+ gcTreeKey,
160
+ } from "./garbageCollectionConstants";
161
161
  import {
162
162
  channelToDataStore,
163
163
  IDataStoreAliasMessage,
@@ -166,6 +166,15 @@ import {
166
166
  import { BindBatchTracker } from "./batchTracker";
167
167
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
168
168
  import { ScheduleManager } from "./scheduleManager";
169
+ import {
170
+ BatchMessage,
171
+ IBatchCheckpoint,
172
+ OpCompressor,
173
+ OpDecompressor,
174
+ Outbox,
175
+ OpSplitter,
176
+ RemoteMessageProcessor,
177
+ } from "./opLifecycle";
169
178
 
170
179
  export enum ContainerMessageType {
171
180
  // An op to be delivered to store
@@ -187,16 +196,6 @@ export enum ContainerMessageType {
187
196
  Alias = "alias",
188
197
  }
189
198
 
190
- export interface IChunkedOp {
191
- chunkId: number;
192
-
193
- totalChunks: number;
194
-
195
- contents: string;
196
-
197
- originalType: MessageType | ContainerMessageType;
198
- }
199
-
200
199
  export interface ContainerRuntimeMessage {
201
200
  contents: any;
202
201
  type: ContainerMessageType;
@@ -209,6 +208,7 @@ export interface ISummaryBaseConfiguration {
209
208
  initialSummarizerDelayMs: number;
210
209
 
211
210
  /**
211
+ * @deprecated
212
212
  * Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
213
213
  * This defaults to false (disabled) and must be explicitly set to true to enable.
214
214
  */
@@ -229,11 +229,6 @@ export interface ISummaryBaseConfiguration {
229
229
 
230
230
  export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfiguration {
231
231
  state: "enabled";
232
- /**
233
- * @deprecated Please move all implementations to {@link ISummaryConfigurationHeuristics.minIdleTime} and
234
- * {@link ISummaryConfigurationHeuristics.maxIdleTime} instead.
235
- */
236
- idleTime?: number;
237
232
  /**
238
233
  * Defines the maximum allowed time, since the last received Ack, before running the summary
239
234
  * with reason maxTime.
@@ -279,6 +274,17 @@ export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfigurati
279
274
  * For example: (multiplier) * (number of non-runtime ops) = weighted number of non-runtime ops
280
275
  */
281
276
  nonRuntimeOpWeight: number;
277
+
278
+ /**
279
+ * Number of ops since last summary needed before a non-runtime op can trigger running summary heuristics.
280
+ *
281
+ * Note: Any runtime ops sent before the threshold is reached will trigger heuristics normally.
282
+ * This threshold ONLY applies to non-runtime ops triggering summaries.
283
+ *
284
+ * For example: Say the threshold is 20. Sending 19 non-runtime ops will not trigger any heuristic checks.
285
+ * Sending the 20th non-runtime op will trigger the heuristic checks for summarizing.
286
+ */
287
+ nonRuntimeHeuristicThreshold?: number;
282
288
  }
283
289
 
284
290
  export interface ISummaryConfigurationDisableSummarizer {
@@ -318,6 +324,8 @@ export const DefaultSummaryConfiguration: ISummaryConfiguration = {
318
324
  nonRuntimeOpWeight: 0.1,
319
325
 
320
326
  runtimeOpWeight: 1.0,
327
+
328
+ nonRuntimeHeuristicThreshold: 20,
321
329
  };
322
330
 
323
331
  export interface IGCRuntimeOptions {
@@ -416,6 +424,22 @@ export interface ISummaryRuntimeOptions {
416
424
  summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
417
425
  }
418
426
 
427
+ /**
428
+ * Options for op compression.
429
+ * @experimental - Not ready for use
430
+ */
431
+ export interface ICompressionRuntimeOptions {
432
+ /**
433
+ * The minimum size the batch's payload must exceed before the batch's contents will be compressed.
434
+ */
435
+ readonly minimumBatchSizeInBytes: number;
436
+
437
+ /**
438
+ * The compression algorithm that will be used to compress the op.
439
+ */
440
+ readonly compressionAlgorithm: CompressionAlgorithms;
441
+ }
442
+
419
443
  /**
420
444
  * Options for container runtime.
421
445
  */
@@ -442,6 +466,22 @@ export interface IContainerRuntimeOptions {
442
466
  * Save enough runtime state to be able to serialize upon request and load to the same state in a new container.
443
467
  */
444
468
  readonly enableOfflineLoad?: boolean;
469
+ /**
470
+ * Enables the runtime to compress ops. Compression is disabled when undefined.
471
+ * @experimental Not ready for use.
472
+ */
473
+ readonly compressionOptions?: ICompressionRuntimeOptions;
474
+ /**
475
+ * If specified, when in FlushMode.TurnBased, if the size of the ops between JS turns exceeds this value,
476
+ * an error will be thrown and the container will close.
477
+ *
478
+ * If unspecified, the limit is 950 * 1024.
479
+ *
480
+ * 'Infinity' will disable any limit.
481
+ *
482
+ * @experimental This config should be driven by the connection with the service and will be moved in the future.
483
+ */
484
+ readonly maxBatchSizeInBytes?: number;
445
485
  }
446
486
 
447
487
  /**
@@ -467,6 +507,13 @@ export enum RuntimeHeaders {
467
507
  viaHandle = "viaHandle",
468
508
  }
469
509
 
510
+ /**
511
+ * Available compression algorithms for op compression.
512
+ */
513
+ export enum CompressionAlgorithms {
514
+ lz4 = "lz4",
515
+ }
516
+
470
517
  /**
471
518
  * @deprecated
472
519
  * Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
@@ -513,6 +560,12 @@ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconn
513
560
 
514
561
  const defaultFlushMode = FlushMode.TurnBased;
515
562
 
563
+ // The actual limit is 1Mb (socket.io and Kafka limits)
564
+ // We can't estimate it fully, as we
565
+ // - do not know what properties relay service will add
566
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
567
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
568
+
516
569
  /**
517
570
  * @deprecated - use ContainerRuntimeMessage instead
518
571
  */
@@ -530,41 +583,7 @@ export enum RuntimeMessage {
530
583
  * @deprecated - please use version in driver-utils
531
584
  */
532
585
  export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
533
- if ((Object.values(RuntimeMessage) as string[]).includes(message.type)) {
534
- return true;
535
- }
536
- return false;
537
- }
538
-
539
- /**
540
- * Unpacks runtime messages
541
- *
542
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
543
- * @param message - message (as it observed in storage / service)
544
- * @returns unpacked runtime message
545
- *
546
- * @internal
547
- */
548
- export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
549
- if (message.type === MessageType.Operation) {
550
- // legacy op format?
551
- if (message.contents.address !== undefined && message.contents.type === undefined) {
552
- message.type = ContainerMessageType.FluidDataStoreOp;
553
- } else {
554
- // new format
555
- const innerContents = message.contents as ContainerRuntimeMessage;
556
- assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
557
- message.type = innerContents.type;
558
- message.contents = innerContents.contents;
559
- }
560
- return true;
561
- } else {
562
- // Legacy format, but it's already "unpacked",
563
- // i.e. message.type is actually ContainerMessageType.
564
- // Or it's non-runtime message.
565
- // Nothing to do in such case.
566
- return false;
567
- }
586
+ return (Object.values(RuntimeMessage) as string[]).includes(message.type);
568
587
  }
569
588
 
570
589
  /**
@@ -609,6 +628,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
609
628
  * @param requestHandler - Request handlers for the container runtime
610
629
  * @param runtimeOptions - Additional options to be passed to the runtime
611
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.
612
633
  */
613
634
  public static async load(
614
635
  context: IContainerContext,
@@ -617,6 +638,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
617
638
  runtimeOptions: IContainerRuntimeOptions = {},
618
639
  containerScope: FluidObject = context.scope,
619
640
  existing?: boolean,
641
+ containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntime
620
642
  ): Promise<ContainerRuntime> {
621
643
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
622
644
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -635,6 +657,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
635
657
  loadSequenceNumberVerification = "close",
636
658
  flushMode = defaultFlushMode,
637
659
  enableOfflineLoad = false,
660
+ compressionOptions = {
661
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
662
+ compressionAlgorithm: CompressionAlgorithms.lz4
663
+ },
664
+ maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
638
665
  } = runtimeOptions;
639
666
 
640
667
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
@@ -697,7 +724,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
697
724
  }
698
725
  }
699
726
 
700
- const runtime = new ContainerRuntime(
727
+ const runtime = new containerRuntimeCtor(
701
728
  context,
702
729
  registry,
703
730
  metadata,
@@ -710,6 +737,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
710
737
  loadSequenceNumberVerification,
711
738
  flushMode,
712
739
  enableOfflineLoad,
740
+ compressionOptions,
741
+ maxBatchSizeInBytes,
713
742
  },
714
743
  containerScope,
715
744
  logger,
@@ -725,7 +754,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
725
754
  pendingRuntimeState.savedOps = [];
726
755
  }
727
756
 
728
- await runtime.getSnapshotBlobs();
757
+ // Initialize the base state of the runtime before it's returned.
758
+ await runtime.initializeBaseState();
729
759
 
730
760
  return runtime;
731
761
  }
@@ -787,6 +817,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
787
817
 
788
818
  // internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
789
819
  private readonly mc: MonitoringContext;
820
+
790
821
  private readonly summarizerClientElection?: SummarizerClientElection;
791
822
  /**
792
823
  * summaryManager will only be created if this client is permitted to spawn a summarizing client
@@ -802,8 +833,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
802
833
  private readonly defaultMaxConsecutiveReconnects = 7;
803
834
 
804
835
  private _orderSequentiallyCalls: number = 0;
805
- private _flushMode: FlushMode;
806
- private flushTrigger = false;
836
+ private readonly _flushMode: FlushMode;
837
+ private flushMicroTaskExists = false;
807
838
 
808
839
  private _connected: boolean;
809
840
 
@@ -847,32 +878,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
847
878
  * It is created only by summarizing container (i.e. one with clientType === "summarizer")
848
879
  */
849
880
  private readonly _summarizer?: Summarizer;
850
- private readonly deltaSender: IDeltaSender;
851
881
  private readonly scheduleManager: ScheduleManager;
852
882
  private readonly blobManager: BlobManager;
853
883
  private readonly pendingStateManager: PendingStateManager;
854
-
855
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
856
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
857
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
858
- // payloads. That number represents final (compressed) bits (once compression is implemented).
859
- private readonly pendingAttachBatch = new BatchManager(64 * 1024);
860
- private readonly pendingBatch = new BatchManager();
884
+ private readonly outbox: Outbox;
861
885
 
862
886
  private readonly garbageCollector: IGarbageCollector;
863
887
 
864
- // Local copy of incomplete received chunks.
865
- private readonly chunkMap: Map<string, string[]>;
866
-
867
888
  private readonly dataStores: DataStores;
889
+ private readonly remoteMessageProcessor: RemoteMessageProcessor;
868
890
 
869
891
  /** The last message processed at the time of the last summary. */
870
892
  private messageAtLastSummary: ISummaryMetadataMessage | undefined;
871
893
 
872
- private get emptyBatch() {
873
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
874
- }
875
-
876
894
  private get summarizer(): Summarizer {
877
895
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
878
896
  return this._summarizer;
@@ -943,7 +961,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
943
961
  */
944
962
  private nextSummaryNumber: number;
945
963
 
946
- private constructor(
964
+ /**
965
+ * @internal
966
+ */
967
+ protected constructor(
947
968
  private readonly context: IContainerContext,
948
969
  private readonly registry: IFluidDataStoreRegistry,
949
970
  metadata: IContainerRuntimeMetadata | undefined,
@@ -965,10 +986,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
965
986
  },
966
987
  ) {
967
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
+
968
1010
  this.messageAtLastSummary = metadata?.message;
969
1011
 
970
1012
  this._connected = this.context.connected;
971
- this.chunkMap = new Map<string, string[]>(chunks);
1013
+ this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
972
1014
 
973
1015
  this.handleContext = new ContainerFluidHandleContext("", this);
974
1016
 
@@ -993,6 +1035,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
993
1035
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
994
1036
  const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
995
1037
 
1038
+ const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1039
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
1040
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
1041
+ // which dictates the value is either undefined or exactly 5 days in ms.
1042
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
1043
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
1044
+ }
1045
+
996
1046
  this.garbageCollector = GarbageCollector.create({
997
1047
  runtime: this,
998
1048
  gcOptions: this.runtimeOptions.gcOptions,
@@ -1000,6 +1050,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1000
1050
  baseLogger: this.mc.logger,
1001
1051
  existing,
1002
1052
  metadata,
1053
+ createContainerMetadata: this.createContainerMetadata,
1003
1054
  isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
1004
1055
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1005
1056
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
@@ -1031,7 +1082,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1031
1082
  );
1032
1083
 
1033
1084
  if (baseSnapshot) {
1034
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
1085
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
1035
1086
  }
1036
1087
 
1037
1088
  this.dataStores = new DataStores(
@@ -1060,7 +1111,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1060
1111
  packagePath,
1061
1112
  ),
1062
1113
  new Map<string, string>(dataStoreAliasMap),
1063
- this.garbageCollector.writeDataAtRoot,
1064
1114
  );
1065
1115
 
1066
1116
  this.blobManager = new BlobManager(
@@ -1084,8 +1134,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1084
1134
  ChildLogger.create(this.logger, "ScheduleManager"),
1085
1135
  );
1086
1136
 
1087
- this.deltaSender = this.deltaManager;
1088
-
1089
1137
  this.pendingStateManager = new PendingStateManager(
1090
1138
  {
1091
1139
  applyStashedOp: this.applyStashedOp.bind(this),
@@ -1093,15 +1141,25 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1093
1141
  close: this.closeFn,
1094
1142
  connected: () => this.connected,
1095
1143
  flush: this.flush.bind(this),
1096
- flushMode: () => this.flushMode,
1097
1144
  reSubmit: this.reSubmit.bind(this),
1098
- setFlushMode: (mode) => this.setFlushMode(mode),
1145
+ rollback: this.rollback.bind(this),
1146
+ orderSequentially: this.orderSequentially.bind(this),
1099
1147
  },
1100
- this._flushMode,
1101
1148
  pendingRuntimeState?.pending);
1102
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
+
1103
1161
  this.context.quorum.on("removeMember", (clientId: string) => {
1104
- this.clearPartialChunks(clientId);
1162
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1105
1163
  });
1106
1164
 
1107
1165
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
@@ -1150,7 +1208,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1150
1208
  // if summaries are enabled and we are not the summarizer client.
1151
1209
  const defaultAction = () => {
1152
1210
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
1153
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
1211
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1154
1212
  // unregister default to no log on every op after falling behind
1155
1213
  // and register summary ack handler to re-register this handler
1156
1214
  // after successful summary
@@ -1217,26 +1275,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1217
1275
  ...getDeviceSpec(),
1218
1276
  });
1219
1277
 
1220
- let loadSummaryNumber: number;
1221
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
1222
- // get the values from the metadata blob.
1223
- if (existing) {
1224
- this.createContainerMetadata = {
1225
- createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
1226
- createContainerTimestamp: metadata?.createContainerTimestamp,
1227
- };
1228
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
1229
- // the count is reset to 0.
1230
- loadSummaryNumber = metadata?.summaryNumber ?? 0;
1231
- } else {
1232
- this.createContainerMetadata = {
1233
- createContainerRuntimeVersion: pkgVersion,
1234
- createContainerTimestamp: Date.now(),
1235
- };
1236
- loadSummaryNumber = 0;
1237
- }
1238
- this.nextSummaryNumber = loadSummaryNumber + 1;
1239
-
1240
1278
  this.logger.sendTelemetryEvent({
1241
1279
  eventName: "ContainerLoadStats",
1242
1280
  ...this.createContainerMetadata,
@@ -1251,6 +1289,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1251
1289
  BindBatchTracker(this, this.logger);
1252
1290
  }
1253
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
+
1254
1300
  public dispose(error?: Error): void {
1255
1301
  if (this._disposed) {
1256
1302
  return;
@@ -1359,10 +1405,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1359
1405
  const wait = typeof request.headers?.[RuntimeHeaders.wait] === "boolean"
1360
1406
  ? request.headers?.[RuntimeHeaders.wait]
1361
1407
  : true;
1408
+ const viaHandle = typeof request.headers?.[RuntimeHeaders.viaHandle] === "boolean"
1409
+ ? request.headers?.[RuntimeHeaders.viaHandle]
1410
+ : false;
1362
1411
 
1363
1412
  await this.dataStores.waitIfPendingAlias(id);
1364
1413
  const internalId = this.internalId(id);
1365
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
1414
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
1366
1415
 
1367
1416
  /**
1368
1417
  * If GC should run and this an external app request with "externalRequest" header, we need to return
@@ -1411,7 +1460,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1411
1460
  addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
1412
1461
  }
1413
1462
 
1414
- private addContainerStateToSummary(
1463
+ protected addContainerStateToSummary(
1415
1464
  summaryTree: ISummaryTreeWithStats,
1416
1465
  fullTree: boolean,
1417
1466
  trackState: boolean,
@@ -1419,8 +1468,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1419
1468
  ) {
1420
1469
  this.addMetadataToSummary(summaryTree);
1421
1470
 
1422
- if (this.chunkMap.size > 0) {
1423
- const content = JSON.stringify([...this.chunkMap]);
1471
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
1472
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
1424
1473
  addBlobToSummary(summaryTree, chunksBlobName, content);
1425
1474
  }
1426
1475
 
@@ -1441,11 +1490,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1441
1490
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
1442
1491
  }
1443
1492
 
1444
- if (this.garbageCollector.writeDataAtRoot) {
1445
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1446
- if (gcSummary !== undefined) {
1447
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1448
- }
1493
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
1494
+ if (gcSummary !== undefined) {
1495
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
1449
1496
  }
1450
1497
  }
1451
1498
 
@@ -1617,37 +1664,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1617
1664
  public process(messageArg: ISequencedDocumentMessage, local: boolean) {
1618
1665
  this.verifyNotClosed();
1619
1666
 
1620
- // Do shallow copy of message, as methods below will modify it.
1621
- // There might be multiple container instances receiving same message
1622
- // We do not need to make deep copy, as each layer will just replace message.content itself,
1623
- // but would not modify contents details
1624
- let message = { ...messageArg };
1625
-
1626
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
1627
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
1628
- // Old ops may contain empty string (I assume noops).
1629
- if (typeof message.contents === "string" && message.contents !== "") {
1630
- message.contents = JSON.parse(message.contents);
1631
- }
1632
-
1633
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
1634
- // This format was not shipped to production workflows.
1635
- const runtimeMessage = unpackRuntimeMessage(message);
1636
-
1637
1667
  if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
1638
1668
  this.savedOps.push(messageArg);
1639
1669
  }
1640
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
+
1641
1681
  // Surround the actual processing of the operation with messages to the schedule manager indicating
1642
1682
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
1643
1683
  // messages once a batch has been fully processed.
1644
1684
  this.scheduleManager.beforeOpProcessing(message);
1645
1685
 
1646
1686
  try {
1647
- // Chunk processing must come first given that we will transform the message to the unchunked version
1648
- // once all pieces are available
1649
- message = this.processRemoteChunkedMessage(message);
1650
-
1651
1687
  let localOpMetadata: unknown;
1652
1688
  if (local && runtimeMessage) {
1653
1689
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1767,143 +1803,35 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1767
1803
  private async getRootDataStoreChannel(id: string, wait = true): Promise<IFluidDataStoreChannel> {
1768
1804
  await this.dataStores.waitIfPendingAlias(id);
1769
1805
  const internalId = this.internalId(id);
1770
- const context = await this.dataStores.getDataStore(internalId, wait);
1806
+ const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1771
1807
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1772
1808
  return context.realize();
1773
1809
  }
1774
1810
 
1775
- public setFlushMode(mode: FlushMode): void {
1776
- if (mode === this._flushMode) {
1777
- return;
1778
- }
1779
-
1780
- this.mc.logger.sendTelemetryEvent({
1781
- eventName: "FlushMode Updated",
1782
- old: this._flushMode,
1783
- new: mode,
1784
- });
1785
-
1786
- // Flush any pending batches if switching to immediate
1787
- if (mode === FlushMode.Immediate) {
1788
- this.flush();
1789
- }
1790
-
1791
- this._flushMode = mode;
1792
-
1793
- // Let the PendingStateManager know that FlushMode has been updated.
1794
- this.pendingStateManager.onFlushModeUpdated(mode);
1795
- }
1796
-
1797
- public flush(): void {
1811
+ /**
1812
+ * Flush the pending ops manually.
1813
+ * This method is expected to be called at the end of a batch.
1814
+ */
1815
+ private flush(): void {
1798
1816
  assert(this._orderSequentiallyCalls === 0,
1799
1817
  0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1800
1818
 
1801
- this.flushBatch(this.pendingAttachBatch.popBatch());
1802
- this.flushBatch(this.pendingBatch.popBatch());
1803
-
1804
- assert(this.emptyBatch, 0x3cf /* reentrancy */);
1819
+ this.outbox.flush();
1820
+ assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1805
1821
  }
1806
1822
 
1807
- protected flushBatch(batch: BatchMessage[]): void {
1808
- const length = batch.length;
1809
-
1810
- if (length > 1) {
1811
- batch[0].metadata = { ...batch[0].metadata, batch: true };
1812
- batch[length - 1].metadata = { ...batch[length - 1].metadata, batch: false };
1813
-
1814
- // This assert fires for the following reason (there might be more cases like that):
1815
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1816
- // i.e. in the middle of op processing!
1817
- // Sending ops while processing ops is not good idea - it's not defined when
1818
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1819
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1820
- // Tracked via ADO #1834
1821
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1822
- // "Batch should be generated synchronously, without processing ops in the middle!");
1823
- }
1824
-
1825
- let clientSequenceNumber: number = -1;
1826
-
1827
- // Did we disconnect in the middle of turn-based batch?
1828
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1829
- if (this.canSendOps()) {
1830
- if (this.context.submitBatchFn !== undefined) {
1831
- const batchToSend: IBatchMessage[] = [];
1832
- for (const message of batch) {
1833
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1834
- }
1835
- // returns clientSequenceNumber of last message in a batch
1836
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1837
- } else {
1838
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1839
- // version that has support for batches (submitBatchFn)
1840
- for (const message of batch) {
1841
- clientSequenceNumber = this.context.submitFn(
1842
- MessageType.Operation,
1843
- message.deserializedContent,
1844
- true, // batch
1845
- message.metadata);
1846
- }
1847
-
1848
- this.deltaSender.flush();
1849
- }
1850
-
1851
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1852
- clientSequenceNumber -= batch.length - 1;
1853
- assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1854
- }
1855
-
1856
- // Let the PendingStateManager know that a message was submitted.
1857
- // In future, need to shift toward keeping batch as a whole!
1858
- for (const message of batch) {
1859
- this.pendingStateManager.onSubmitMessage(
1860
- message.deserializedContent.type,
1861
- clientSequenceNumber,
1862
- message.referenceSequenceNumber,
1863
- message.deserializedContent.contents,
1864
- message.localOpMetadata,
1865
- message.metadata,
1866
- );
1867
- clientSequenceNumber++;
1868
- }
1869
-
1870
- this.pendingStateManager.onFlush();
1871
- }
1872
-
1873
- public orderSequentially(callback: () => void): void {
1874
- // If flush mode is already TurnBased we are either
1875
- // nested in another orderSequentially, or
1876
- // the app is flushing manually, in which
1877
- // case this invocation doesn't own
1878
- // flushing.
1879
- if (this.flushMode === FlushMode.TurnBased) {
1880
- this.trackOrderSequentiallyCalls(callback);
1881
- return;
1882
- }
1883
-
1884
- const savedFlushMode = this.flushMode;
1885
- this.setFlushMode(FlushMode.TurnBased);
1886
-
1887
- try {
1888
- this.trackOrderSequentiallyCalls(callback);
1889
- this.flush();
1890
- } finally {
1891
- this.setFlushMode(savedFlushMode);
1892
- }
1893
- }
1894
-
1895
- private trackOrderSequentiallyCalls(callback: () => void): void {
1896
- let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
1823
+ public orderSequentially<T>(callback: () => T): T {
1824
+ let checkpoint: IBatchCheckpoint | undefined;
1825
+ let result: T;
1897
1826
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1898
1827
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1899
1828
  // 1. It would not help, as we flush attach ops as they become available.
1900
1829
  // 2. There is no way to undo process of data store creation.
1901
- checkpoint = this.pendingBatch.checkpoint();
1830
+ checkpoint = this.outbox.checkpoint().mainBatch;
1902
1831
  }
1903
-
1904
1832
  try {
1905
1833
  this._orderSequentiallyCalls++;
1906
- callback();
1834
+ result = callback();
1907
1835
  } catch (error) {
1908
1836
  if (checkpoint) {
1909
1837
  // This will throw and close the container if rollback fails
@@ -1931,6 +1859,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1931
1859
  } finally {
1932
1860
  this._orderSequentiallyCalls--;
1933
1861
  }
1862
+
1863
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1864
+ this.flush();
1865
+ }
1866
+ return result;
1934
1867
  }
1935
1868
 
1936
1869
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
@@ -1980,6 +1913,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1980
1913
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1981
1914
  }
1982
1915
 
1916
+ /**
1917
+ * Are we in the middle of batching ops together?
1918
+ */
1919
+ private currentlyBatching() {
1920
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1921
+ }
1922
+
1983
1923
  public getQuorum(): IQuorumClients {
1984
1924
  return this.context.quorum;
1985
1925
  }
@@ -2192,31 +2132,35 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2192
2132
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
2193
2133
  * After GC has run, called to notify this container's nodes of routes that are used in it.
2194
2134
  * @param usedRoutes - The routes that are used in all nodes in this Container.
2195
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
2196
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
2197
2135
  */
2198
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
2136
+ public updateUsedRoutes(usedRoutes: string[]) {
2199
2137
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
2200
2138
  // summarizing is required and asserted by the the summarizer node. We are the root and are
2201
2139
  // always referenced, so the used routes is only self-route (empty string).
2202
2140
  this.summarizerNode.updateUsedRoutes([""]);
2203
2141
 
2142
+ const blobManagerUsedRoutes: string[] = [];
2204
2143
  const dataStoreUsedRoutes: string[] = [];
2205
2144
  for (const route of usedRoutes) {
2206
- if (route.split("/")[1] !== BlobManager.basePath) {
2145
+ if (this.isBlobPath(route)) {
2146
+ blobManagerUsedRoutes.push(route);
2147
+ } else {
2207
2148
  dataStoreUsedRoutes.push(route);
2208
2149
  }
2209
2150
  }
2210
2151
 
2211
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
2152
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
2153
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2212
2154
  }
2213
2155
 
2214
2156
  /**
2215
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
2216
- * scenarios with accessing deleted content.
2217
- * @param unusedRoutes - The routes that are unused in all data stores in this Container.
2157
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
2158
+ * tombstones.
2159
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
2160
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
2161
+ * are deleted.
2218
2162
  */
2219
- public deleteUnusedRoutes(unusedRoutes: string[]) {
2163
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
2220
2164
  const blobManagerUnusedRoutes: string[] = [];
2221
2165
  const dataStoreUnusedRoutes: string[] = [];
2222
2166
  for (const route of unusedRoutes) {
@@ -2227,8 +2171,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2227
2171
  }
2228
2172
  }
2229
2173
 
2230
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2231
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
2174
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
2175
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
2232
2176
  }
2233
2177
 
2234
2178
  /**
@@ -2326,7 +2270,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2326
2270
  },
2327
2271
  );
2328
2272
 
2329
- assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2273
+ assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2330
2274
 
2331
2275
  let latestSnapshotVersionId: string | undefined;
2332
2276
  if (refreshLatestAck) {
@@ -2335,20 +2279,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2335
2279
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2336
2280
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
2337
2281
 
2338
- if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2339
- // We need to catch up to the latest summary's reference sequence number before pausing.
2340
- await PerformanceEvent.timedExecAsync(
2341
- summaryNumberLogger,
2342
- {
2343
- eventName: "WaitingForSeq",
2344
- lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2345
- targetSequenceNumber: latestSnapshotRefSeq,
2346
- lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2347
- },
2348
- async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2349
- { start: true, end: true, cancel: "error" }, // definitely want start event
2350
- );
2351
- }
2282
+ // We might need to catch up to the latest summary's reference sequence number before pausing.
2283
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq,
2284
+ summaryNumberLogger);
2352
2285
  }
2353
2286
 
2354
2287
  try {
@@ -2547,45 +2480,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2547
2480
  }
2548
2481
  }
2549
2482
 
2550
- private processRemoteChunkedMessage(message: ISequencedDocumentMessage) {
2551
- if (message.type !== ContainerMessageType.ChunkedOp) {
2552
- return message;
2553
- }
2554
-
2555
- const clientId = message.clientId;
2556
- const chunkedContent = message.contents as IChunkedOp;
2557
- this.addChunk(clientId, chunkedContent);
2558
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
2559
- const newMessage = { ...message };
2560
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2561
- const serializedContent = this.chunkMap.get(clientId)!.join("");
2562
- newMessage.contents = JSON.parse(serializedContent);
2563
- newMessage.type = chunkedContent.originalType;
2564
- this.clearPartialChunks(clientId);
2565
- return newMessage;
2566
- }
2567
- return message;
2568
- }
2569
-
2570
- private addChunk(clientId: string, chunkedContent: IChunkedOp) {
2571
- let map = this.chunkMap.get(clientId);
2572
- if (map === undefined) {
2573
- map = [];
2574
- this.chunkMap.set(clientId, map);
2575
- }
2576
- assert(chunkedContent.chunkId === map.length + 1,
2577
- 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
2578
- map.push(chunkedContent.contents);
2579
- }
2580
-
2581
- private clearPartialChunks(clientId: string) {
2582
- if (this.chunkMap.has(clientId)) {
2583
- this.chunkMap.delete(clientId);
2584
- }
2585
- }
2586
-
2587
2483
  private hasPendingMessages() {
2588
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
2484
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
2589
2485
  }
2590
2486
 
2591
2487
  private updateDocumentDirtyState(dirty: boolean) {
@@ -2648,7 +2544,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2648
2544
  const serializedContent = JSON.stringify(deserializedContent);
2649
2545
 
2650
2546
  if (this.deltaManager.readOnlyInfo.readonly) {
2651
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
2547
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
2652
2548
  }
2653
2549
 
2654
2550
  const message: BatchMessage = {
@@ -2680,47 +2576,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2680
2576
  // issue than sending.
2681
2577
  // Please note that this does not change file format, so it can be disabled in the future if this
2682
2578
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
2683
- if (type === ContainerMessageType.Attach &&
2579
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
2684
2580
  this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
2685
- if (!this.pendingAttachBatch.push(message)) {
2686
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
2687
- // when queue is not empty.
2688
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
2689
- this.flushBatch(this.pendingAttachBatch.popBatch());
2690
- if (!this.pendingAttachBatch.push(message)) {
2691
- throw new GenericError(
2692
- "BatchTooLarge",
2693
- /* error */ undefined,
2694
- {
2695
- opSize: message.contents.length,
2696
- count: this.pendingAttachBatch.length,
2697
- limit: this.pendingAttachBatch.limit,
2698
- });
2699
- }
2700
- }
2581
+ this.outbox.submitAttach(message);
2701
2582
  } else {
2702
- if (!this.pendingBatch.push(message)) {
2703
- throw new GenericError(
2704
- "BatchTooLarge",
2705
- /* error */ undefined,
2706
- {
2707
- opSize: message.contents.length,
2708
- count: this.pendingBatch.length,
2709
- limit: this.pendingBatch.limit,
2710
- });
2711
- }
2583
+ this.outbox.submit(message);
2712
2584
  }
2713
2585
 
2714
- if (this._flushMode !== FlushMode.TurnBased) {
2586
+ if (!this.currentlyBatching()) {
2715
2587
  this.flush();
2716
- } else if (!this.flushTrigger) {
2717
- this.flushTrigger = true;
2588
+ } else if (!this.flushMicroTaskExists) {
2589
+ this.flushMicroTaskExists = true;
2718
2590
  // Queue a microtask to detect the end of the turn and force a flush.
2719
2591
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
2720
2592
  Promise.resolve().then(() => {
2721
- this.flushTrigger = false;
2593
+ this.flushMicroTaskExists = false;
2722
2594
  this.flush();
2723
- });
2595
+ }).catch((error) => { this.closeFn(error as GenericError) });
2724
2596
  }
2725
2597
  } catch (error) {
2726
2598
  this.closeFn(error as GenericError);
@@ -2737,7 +2609,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2737
2609
  assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
2738
2610
 
2739
2611
  // System message should not be sent in the middle of the batch.
2740
- assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
2612
+ assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
2741
2613
 
2742
2614
  // back-compat: ADO #1385: Make this call unconditional in the future
2743
2615
  return this.context.submitSummaryFn !== undefined
@@ -2809,28 +2681,61 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2809
2681
  }
2810
2682
  }
2811
2683
 
2812
- /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2813
- public async refreshLatestSummaryAck(
2814
- proposalHandle: string | undefined,
2815
- ackHandle: string,
2816
- summaryRefSeq: number,
2684
+ private async waitForDeltaManagerToCatchup(
2685
+ latestSnapshotRefSeq: number,
2817
2686
  summaryLogger: ITelemetryLogger,
2818
- ) {
2687
+ ): Promise<void> {
2688
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2689
+ // We need to catch up to the latest summary's reference sequence number before proceeding.
2690
+ await PerformanceEvent.timedExecAsync(
2691
+ summaryLogger,
2692
+ {
2693
+ eventName: "WaitingForSeq",
2694
+ lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2695
+ targetSequenceNumber: latestSnapshotRefSeq,
2696
+ lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2697
+ },
2698
+ async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2699
+ { start: true, end: true, cancel: "error" }, // definitely want start event
2700
+ );
2701
+ }
2702
+ }
2703
+
2704
+ /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2705
+ public async refreshLatestSummaryAck(options: IRefreshSummaryAckOptions) {
2706
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
2819
2707
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
2820
2708
  // The call to fetch the snapshot is very expensive and not always needed.
2821
2709
  // It should only be done by the summarizerNode, if required.
2710
+ // When fetching from storage we will always get the latest version and do not use the ackHandle.
2822
2711
  const snapshotTreeFetcher = async () => {
2823
2712
  const fetchResult = await this.fetchSnapshotFromStorage(
2824
- ackHandle,
2713
+ null,
2825
2714
  summaryLogger,
2826
2715
  {
2827
2716
  eventName: "RefreshLatestSummaryGetSnapshot",
2828
2717
  ackHandle,
2829
2718
  summaryRefSeq,
2830
- fetchLatest: false,
2719
+ fetchLatest: true,
2831
2720
  });
2721
+
2722
+ const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2723
+ summaryLogger.sendTelemetryEvent(
2724
+ {
2725
+ eventName: "LatestSummaryRetrieved",
2726
+ ackHandle,
2727
+ lastSequenceNumber: latestSnapshotRefSeq,
2728
+ targetSequenceNumber: summaryRefSeq,
2729
+ });
2730
+
2731
+ // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
2732
+ // wait for the delta manager to catch up before refreshing the latest Summary.
2733
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq,
2734
+ summaryLogger);
2735
+
2832
2736
  return fetchResult.snapshotTree;
2833
2737
  };
2738
+
2834
2739
  const result = await this.summarizerNode.refreshLatestSummary(
2835
2740
  proposalHandle,
2836
2741
  summaryRefSeq,
@@ -2912,7 +2817,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2912
2817
  }
2913
2818
  }
2914
2819
 
2915
- public async getSnapshotBlobs(): Promise<void> {
2820
+ private async initializeBaseSnapshotBlobs(): Promise<void> {
2916
2821
  if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) ||
2917
2822
  this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
2918
2823
  return;
@@ -2931,6 +2836,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2931
2836
  // to close current batch.
2932
2837
  this.flush();
2933
2838
 
2839
+ if (this._orderSequentiallyCalls !== 0) {
2840
+ throw new UsageError("can't get state during orderSequentially");
2841
+ }
2842
+
2934
2843
  const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
2935
2844
  if (previousPendingState) {
2936
2845
  return {
@@ -3036,6 +2945,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3036
2945
  throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
3037
2946
  }
3038
2947
  }
2948
+ if (configuration.minIdleTime > configuration.maxIdleTime) {
2949
+ throw new UsageError(`"minIdleTime" [${configuration.minIdleTime}] cannot be greater than "maxIdleTime" [${configuration.maxIdleTime}]`);
2950
+ }
3039
2951
  }
3040
2952
  }
3041
2953
 
@@ -3050,11 +2962,16 @@ const waitForSeq = async (
3050
2962
  // TODO: remove cast to any when actual event is determined
3051
2963
  deltaManager.on("closed" as any, reject);
3052
2964
 
3053
- const handleOp = (message: Pick<ISequencedDocumentMessage, "sequenceNumber">) => {
3054
- if (message.sequenceNumber >= targetSeq) {
3055
- resolve();
3056
- deltaManager.off("op", handleOp);
3057
- }
3058
- };
3059
- deltaManager.on("op", handleOp);
2965
+ // If we already reached target sequence number, simply resolve the promise.
2966
+ if (deltaManager.lastSequenceNumber >= targetSeq) {
2967
+ resolve();
2968
+ } else {
2969
+ const handleOp = (message: Pick<ISequencedDocumentMessage, "sequenceNumber">) => {
2970
+ if (message.sequenceNumber >= targetSeq) {
2971
+ resolve();
2972
+ deltaManager.off("op", handleOp);
2973
+ }
2974
+ };
2975
+ deltaManager.on("op", handleOp);
2976
+ }
3060
2977
  });