@fluidframework/container-runtime 2.0.0-internal.3.2.1 → 2.0.0-internal.3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/containerRuntime.d.ts +32 -53
  2. package/dist/containerRuntime.d.ts.map +1 -1
  3. package/dist/containerRuntime.js +55 -21
  4. package/dist/containerRuntime.js.map +1 -1
  5. package/dist/dataStores.d.ts.map +1 -1
  6. package/dist/dataStores.js +8 -3
  7. package/dist/dataStores.js.map +1 -1
  8. package/dist/deltaManagerSummarizerProxy.d.ts +19 -0
  9. package/dist/deltaManagerSummarizerProxy.d.ts.map +1 -0
  10. package/dist/deltaManagerSummarizerProxy.js +40 -0
  11. package/dist/deltaManagerSummarizerProxy.js.map +1 -0
  12. package/dist/gc/garbageCollection.d.ts +2 -33
  13. package/dist/gc/garbageCollection.d.ts.map +1 -1
  14. package/dist/gc/garbageCollection.js +36 -181
  15. package/dist/gc/garbageCollection.js.map +1 -1
  16. package/dist/gc/gcConfigs.d.ts +22 -0
  17. package/dist/gc/gcConfigs.d.ts.map +1 -0
  18. package/dist/gc/gcConfigs.js +138 -0
  19. package/dist/gc/gcConfigs.js.map +1 -0
  20. package/dist/gc/gcDefinitions.d.ts +101 -3
  21. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  22. package/dist/gc/gcDefinitions.js +8 -3
  23. package/dist/gc/gcDefinitions.js.map +1 -1
  24. package/dist/gc/gcHelpers.d.ts +12 -1
  25. package/dist/gc/gcHelpers.d.ts.map +1 -1
  26. package/dist/gc/gcHelpers.js +55 -1
  27. package/dist/gc/gcHelpers.js.map +1 -1
  28. package/dist/gc/gcSummaryStateTracker.d.ts +1 -2
  29. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  30. package/dist/gc/gcSummaryStateTracker.js +28 -37
  31. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  32. package/dist/gc/index.d.ts +3 -2
  33. package/dist/gc/index.d.ts.map +1 -1
  34. package/dist/gc/index.js +2 -1
  35. package/dist/gc/index.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/opLifecycle/batchManager.d.ts +9 -0
  40. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  41. package/dist/opLifecycle/batchManager.js +19 -2
  42. package/dist/opLifecycle/batchManager.js.map +1 -1
  43. package/dist/opLifecycle/index.d.ts +1 -1
  44. package/dist/opLifecycle/index.d.ts.map +1 -1
  45. package/dist/opLifecycle/index.js +2 -1
  46. package/dist/opLifecycle/index.js.map +1 -1
  47. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  48. package/dist/opLifecycle/opCompressor.js +24 -10
  49. package/dist/opLifecycle/opCompressor.js.map +1 -1
  50. package/dist/opLifecycle/opDecompressor.d.ts +4 -0
  51. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.js +42 -4
  53. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  54. package/dist/opLifecycle/opSplitter.d.ts +14 -2
  55. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  56. package/dist/opLifecycle/opSplitter.js +35 -18
  57. package/dist/opLifecycle/opSplitter.js.map +1 -1
  58. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  59. package/dist/opLifecycle/outbox.js +29 -21
  60. package/dist/opLifecycle/outbox.js.map +1 -1
  61. package/dist/packageVersion.d.ts +1 -1
  62. package/dist/packageVersion.js +1 -1
  63. package/dist/packageVersion.js.map +1 -1
  64. package/dist/storageServiceWithAttachBlobs.d.ts +17 -0
  65. package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -0
  66. package/dist/storageServiceWithAttachBlobs.js +32 -0
  67. package/dist/storageServiceWithAttachBlobs.js.map +1 -0
  68. package/dist/summary/runWhileConnectedCoordinator.d.ts +3 -2
  69. package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  70. package/dist/summary/runWhileConnectedCoordinator.js +5 -4
  71. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  72. package/dist/summary/summarizerTypes.d.ts +2 -0
  73. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  74. package/dist/summary/summarizerTypes.js.map +1 -1
  75. package/lib/containerRuntime.d.ts +32 -53
  76. package/lib/containerRuntime.d.ts.map +1 -1
  77. package/lib/containerRuntime.js +56 -22
  78. package/lib/containerRuntime.js.map +1 -1
  79. package/lib/dataStores.d.ts.map +1 -1
  80. package/lib/dataStores.js +9 -4
  81. package/lib/dataStores.js.map +1 -1
  82. package/lib/deltaManagerSummarizerProxy.d.ts +19 -0
  83. package/lib/deltaManagerSummarizerProxy.d.ts.map +1 -0
  84. package/lib/deltaManagerSummarizerProxy.js +36 -0
  85. package/lib/deltaManagerSummarizerProxy.js.map +1 -0
  86. package/lib/gc/garbageCollection.d.ts +2 -33
  87. package/lib/gc/garbageCollection.d.ts.map +1 -1
  88. package/lib/gc/garbageCollection.js +39 -184
  89. package/lib/gc/garbageCollection.js.map +1 -1
  90. package/lib/gc/gcConfigs.d.ts +22 -0
  91. package/lib/gc/gcConfigs.d.ts.map +1 -0
  92. package/lib/gc/gcConfigs.js +134 -0
  93. package/lib/gc/gcConfigs.js.map +1 -0
  94. package/lib/gc/gcDefinitions.d.ts +101 -3
  95. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  96. package/lib/gc/gcDefinitions.js +7 -2
  97. package/lib/gc/gcDefinitions.js.map +1 -1
  98. package/lib/gc/gcHelpers.d.ts +12 -1
  99. package/lib/gc/gcHelpers.d.ts.map +1 -1
  100. package/lib/gc/gcHelpers.js +53 -0
  101. package/lib/gc/gcHelpers.js.map +1 -1
  102. package/lib/gc/gcSummaryStateTracker.d.ts +1 -2
  103. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  104. package/lib/gc/gcSummaryStateTracker.js +28 -37
  105. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  106. package/lib/gc/index.d.ts +3 -2
  107. package/lib/gc/index.d.ts.map +1 -1
  108. package/lib/gc/index.js +1 -1
  109. package/lib/gc/index.js.map +1 -1
  110. package/lib/index.d.ts +2 -2
  111. package/lib/index.d.ts.map +1 -1
  112. package/lib/index.js.map +1 -1
  113. package/lib/opLifecycle/batchManager.d.ts +9 -0
  114. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  115. package/lib/opLifecycle/batchManager.js +17 -1
  116. package/lib/opLifecycle/batchManager.js.map +1 -1
  117. package/lib/opLifecycle/index.d.ts +1 -1
  118. package/lib/opLifecycle/index.d.ts.map +1 -1
  119. package/lib/opLifecycle/index.js +1 -1
  120. package/lib/opLifecycle/index.js.map +1 -1
  121. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  122. package/lib/opLifecycle/opCompressor.js +25 -11
  123. package/lib/opLifecycle/opCompressor.js.map +1 -1
  124. package/lib/opLifecycle/opDecompressor.d.ts +4 -0
  125. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  126. package/lib/opLifecycle/opDecompressor.js +42 -4
  127. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  128. package/lib/opLifecycle/opSplitter.d.ts +14 -2
  129. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  130. package/lib/opLifecycle/opSplitter.js +35 -18
  131. package/lib/opLifecycle/opSplitter.js.map +1 -1
  132. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  133. package/lib/opLifecycle/outbox.js +30 -22
  134. package/lib/opLifecycle/outbox.js.map +1 -1
  135. package/lib/packageVersion.d.ts +1 -1
  136. package/lib/packageVersion.js +1 -1
  137. package/lib/packageVersion.js.map +1 -1
  138. package/lib/storageServiceWithAttachBlobs.d.ts +17 -0
  139. package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -0
  140. package/lib/storageServiceWithAttachBlobs.js +28 -0
  141. package/lib/storageServiceWithAttachBlobs.js.map +1 -0
  142. package/lib/summary/runWhileConnectedCoordinator.d.ts +3 -2
  143. package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  144. package/lib/summary/runWhileConnectedCoordinator.js +5 -4
  145. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  146. package/lib/summary/summarizerTypes.d.ts +2 -0
  147. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  148. package/lib/summary/summarizerTypes.js.map +1 -1
  149. package/package.json +20 -31
  150. package/src/containerRuntime.ts +92 -76
  151. package/src/dataStores.ts +9 -4
  152. package/src/deltaManagerSummarizerProxy.ts +46 -0
  153. package/src/gc/garbageCollection.ts +50 -290
  154. package/src/gc/gcConfigs.ts +177 -0
  155. package/src/gc/gcDefinitions.ts +110 -4
  156. package/src/gc/gcHelpers.ts +78 -1
  157. package/src/gc/gcSummaryStateTracker.ts +35 -42
  158. package/src/gc/index.ts +8 -2
  159. package/src/index.ts +1 -2
  160. package/src/opLifecycle/README.md +2 -2
  161. package/src/opLifecycle/batchManager.ts +19 -1
  162. package/src/opLifecycle/index.ts +1 -1
  163. package/src/opLifecycle/opCompressor.ts +31 -12
  164. package/src/opLifecycle/opDecompressor.ts +49 -5
  165. package/src/opLifecycle/opSplitter.ts +44 -20
  166. package/src/opLifecycle/outbox.ts +36 -22
  167. package/src/packageVersion.ts +1 -1
  168. package/src/storageServiceWithAttachBlobs.ts +38 -0
  169. package/src/summary/runWhileConnectedCoordinator.ts +7 -7
  170. package/src/summary/summarizerTypes.ts +2 -0
@@ -4,11 +4,12 @@
4
4
  */
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
- import { IsoBuffer } from "@fluidframework/common-utils";
7
+ import { assert, IsoBuffer } from "@fluidframework/common-utils";
8
8
  import { UsageError } from "@fluidframework/container-utils";
9
9
  import { ChildLogger } from "@fluidframework/telemetry-utils";
10
10
  import { compress } from "lz4js";
11
11
  import { CompressionAlgorithms } from "../containerRuntime";
12
+ import { estimateSocketSize } from "./batchManager";
12
13
  import { IBatch, BatchMessage } from "./definitions";
13
14
 
14
15
  /**
@@ -24,21 +25,17 @@ export class OpCompressor {
24
25
  }
25
26
 
26
27
  public compressBatch(batch: IBatch): IBatch {
28
+ assert(
29
+ batch.contentSizeInBytes > 0 && batch.content.length > 0,
30
+ 0x5a4 /* Batch should not be empty */,
31
+ );
32
+
27
33
  const compressionStart = Date.now();
28
34
  const contentsAsBuffer = new TextEncoder().encode(this.serializeBatch(batch));
29
35
  const compressedContents = compress(contentsAsBuffer);
30
36
  const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
31
37
  const duration = Date.now() - compressionStart;
32
38
 
33
- if (batch.contentSizeInBytes > 200000) {
34
- this.logger.sendPerformanceEvent({
35
- eventName: "CompressedBatch",
36
- duration,
37
- sizeBeforeCompression: batch.contentSizeInBytes,
38
- sizeAfterCompression: compressedContent.length,
39
- });
40
- }
41
-
42
39
  const messages: BatchMessage[] = [];
43
40
  messages.push({
44
41
  ...batch.content[0],
@@ -47,15 +44,37 @@ export class OpCompressor {
47
44
  compression: CompressionAlgorithms.lz4,
48
45
  });
49
46
 
47
+ // Add empty placeholder messages to reserve the sequence numbers
50
48
  for (const message of batch.content.slice(1)) {
51
- messages.push({ ...message, contents: undefined });
49
+ messages.push({
50
+ deserializedContent: {
51
+ contents: undefined,
52
+ type: message.deserializedContent.type,
53
+ },
54
+ localOpMetadata: message.localOpMetadata,
55
+ metadata: message.metadata,
56
+ referenceSequenceNumber: message.referenceSequenceNumber,
57
+ });
52
58
  }
53
59
 
54
- return {
60
+ const compressedBatch: IBatch = {
55
61
  contentSizeInBytes: compressedContent.length,
56
62
  content: messages,
57
63
  referenceSequenceNumber: batch.referenceSequenceNumber,
58
64
  };
65
+
66
+ if (batch.contentSizeInBytes > 200000) {
67
+ this.logger.sendPerformanceEvent({
68
+ eventName: "CompressedBatch",
69
+ duration,
70
+ sizeBeforeCompression: batch.contentSizeInBytes,
71
+ sizeAfterCompression: compressedBatch.contentSizeInBytes,
72
+ opCount: compressedBatch.content.length,
73
+ socketSize: estimateSocketSize(compressedBatch),
74
+ });
75
+ }
76
+
77
+ return compressedBatch;
59
78
  }
60
79
 
61
80
  private serializeBatch(batch: IBatch): string {
@@ -6,6 +6,8 @@
6
6
  import { decompress } from "lz4js";
7
7
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
8
  import { assert, IsoBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
9
+ import { ChildLogger } from "@fluidframework/telemetry-utils";
10
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
9
11
  import { CompressionAlgorithms } from "../containerRuntime";
10
12
  import { IMessageProcessingResult } from "./definitions";
11
13
 
@@ -21,6 +23,11 @@ export class OpDecompressor {
21
23
  private activeBatch = false;
22
24
  private rootMessageContents: any | undefined;
23
25
  private processedCount = 0;
26
+ private readonly logger;
27
+
28
+ constructor(logger: ITelemetryLogger) {
29
+ this.logger = ChildLogger.create(logger, "OpDecompressor");
30
+ }
24
31
 
25
32
  public processMessage(message: ISequencedDocumentMessage): IMessageProcessingResult {
26
33
  assert(
@@ -28,7 +35,7 @@ export class OpDecompressor {
28
35
  0x511 /* Only lz4 compression is supported */,
29
36
  );
30
37
 
31
- if (message.metadata?.batch === true && message.compression === CompressionAlgorithms.lz4) {
38
+ if (message.metadata?.batch === true && this.isCompressed(message)) {
32
39
  // Beginning of a compressed batch
33
40
  assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
34
41
  if (message.compression) {
@@ -84,10 +91,7 @@ export class OpDecompressor {
84
91
  };
85
92
  }
86
93
 
87
- if (
88
- message.metadata?.batch === undefined &&
89
- message.compression === CompressionAlgorithms.lz4
90
- ) {
94
+ if (message.metadata?.batch === undefined && this.isCompressed(message)) {
91
95
  // Single compressed message
92
96
  assert(
93
97
  this.activeBatch === false,
@@ -110,6 +114,46 @@ export class OpDecompressor {
110
114
  state: "Skipped",
111
115
  };
112
116
  }
117
+
118
+ private isCompressed(message: ISequencedDocumentMessage) {
119
+ if (message.compression === CompressionAlgorithms.lz4) {
120
+ return true;
121
+ }
122
+
123
+ /**
124
+ * Back-compat self healing mechanism for ADO:3538, as loaders from
125
+ * version client_v2.0.0-internal.1.2.0 to client_v2.0.0-internal.2.2.0 do not
126
+ * support adding the proper compression metadata to compressed messages submitted
127
+ * by the runtime. Should be removed after the loader reaches sufficient saturation
128
+ * for a version greater or equal than client_v2.0.0-internal.2.2.0.
129
+ *
130
+ * The condition holds true for compressed messages, regardless of metadata. We are ultimately
131
+ * looking for a message with a single property `packedContents` inside `contents`, of type 'string'
132
+ * with a base64 encoded value.
133
+ */
134
+ try {
135
+ if (
136
+ typeof message.contents === "object" &&
137
+ Object.keys(message.contents).length === 1 &&
138
+ message.contents?.packedContents !== undefined &&
139
+ typeof message.contents?.packedContents === "string" &&
140
+ message.contents.packedContents.length > 0 &&
141
+ IsoBuffer.from(message.contents.packedContents, "base64").toString("base64") ===
142
+ message.contents.packedContents
143
+ ) {
144
+ this.logger.sendTelemetryEvent({
145
+ eventName: "LegacyCompression",
146
+ type: message.type,
147
+ batch: message.metadata?.batch,
148
+ });
149
+ return true;
150
+ }
151
+ } catch (err) {
152
+ return false;
153
+ }
154
+
155
+ return false;
156
+ }
113
157
  }
114
158
 
115
159
  // We should not be mutating the input message nor its metadata
@@ -13,6 +13,7 @@ import {
13
13
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
14
14
  import { ChildLogger } from "@fluidframework/telemetry-utils";
15
15
  import { ContainerMessageType, ContainerRuntimeMessage } from "../containerRuntime";
16
+ import { estimateSocketSize } from "./batchManager";
16
17
  import { BatchMessage, IBatch, IChunkedOp, IMessageProcessingResult } from "./definitions";
17
18
 
18
19
  /**
@@ -28,7 +29,7 @@ export class OpSplitter {
28
29
  private readonly submitBatchFn:
29
30
  | ((batch: IBatchMessage[], referenceSequenceNumber?: number) => number)
30
31
  | undefined,
31
- private readonly chunkSizeInBytes: number,
32
+ public readonly chunkSizeInBytes: number,
32
33
  private readonly maxBatchSizeInBytes: number,
33
34
  logger: ITelemetryLogger,
34
35
  ) {
@@ -161,7 +162,15 @@ export class OpSplitter {
161
162
  );
162
163
 
163
164
  const restOfMessages = batch.content.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
164
- const chunks = splitOp(firstMessage, this.chunkSizeInBytes);
165
+ const socketSize = estimateSocketSize(batch);
166
+ const chunks = splitOp(
167
+ firstMessage,
168
+ this.chunkSizeInBytes,
169
+ // If we estimate that the socket batch size will exceed the batch limit
170
+ // we will inject an empty op to minimize the risk of the payload failing due to
171
+ // the overhead from the trailing empty ops in the batch.
172
+ socketSize >= this.maxBatchSizeInBytes,
173
+ );
165
174
 
166
175
  assert(this.submitBatchFn !== undefined, 0x519 /* We don't support old loaders */);
167
176
  // Send the first N-1 chunks immediately
@@ -181,11 +190,13 @@ export class OpSplitter {
181
190
  );
182
191
 
183
192
  this.logger.sendPerformanceEvent({
184
- eventName: "Chunked compressed batch",
193
+ // Used to be "Chunked compressed batch"
194
+ eventName: "CompressedChunkedBatch",
185
195
  length: batch.content.length,
186
196
  sizeInBytes: batch.contentSizeInBytes,
187
197
  chunks: chunks.length,
188
198
  chunkSizeInBytes: this.chunkSizeInBytes,
199
+ socketSize,
189
200
  });
190
201
 
191
202
  return {
@@ -214,7 +225,23 @@ const chunkToBatchMessage = (
214
225
  };
215
226
  };
216
227
 
217
- export const splitOp = (op: BatchMessage, chunkSizeInBytes: number): IChunkedOp[] => {
228
+ /**
229
+ * Splits an op into smaller ops (chunks), based on the size of the op and the `chunkSizeInBytes` parameter.
230
+ *
231
+ * The last op of the result will be bundled with empty ops in the same batch. There is a risk of the batch payload
232
+ * exceeding the 1MB limit due to the overhead from the empty ops. If the last op is large, the risk is even higher.
233
+ * To minimize the odds, an extra empty op can be added to the result using the `extraOp` parameter.
234
+ *
235
+ * @param op - the op to be split
236
+ * @param chunkSizeInBytes - how large should the chunks be
237
+ * @param extraOp - should an extra empty op be added to the result
238
+ * @returns an array of chunked ops
239
+ */
240
+ export const splitOp = (
241
+ op: BatchMessage,
242
+ chunkSizeInBytes: number,
243
+ extraOp: boolean = false,
244
+ ): IChunkedOp[] => {
218
245
  const chunks: IChunkedOp[] = [];
219
246
  assert(
220
247
  op.contents !== undefined && op.contents !== null,
@@ -222,36 +249,33 @@ export const splitOp = (op: BatchMessage, chunkSizeInBytes: number): IChunkedOp[
222
249
  );
223
250
 
224
251
  const contentLength = op.contents.length;
225
- const chunkCount = Math.floor((contentLength - 1) / chunkSizeInBytes) + 2;
252
+ const chunkCount = Math.floor((contentLength - 1) / chunkSizeInBytes) + 1 + (extraOp ? 1 : 0);
226
253
  let offset = 0;
227
- for (let i = 1; i < chunkCount; i++) {
254
+ for (let chunkId = 1; chunkId <= chunkCount; chunkId++) {
228
255
  const chunk: IChunkedOp = {
229
- chunkId: i,
256
+ chunkId,
230
257
  contents: op.contents.substr(offset, chunkSizeInBytes),
231
258
  originalType: op.deserializedContent.type,
232
259
  totalChunks: chunkCount,
233
260
  };
234
261
 
262
+ if (chunkId === chunkCount) {
263
+ // We don't need to port these to all the chunks,
264
+ // as we rebuild the original op when we process the
265
+ // last chunk, therefore it is the only one that needs it.
266
+ chunk.originalMetadata = op.metadata;
267
+ chunk.originalCompression = op.compression;
268
+ }
269
+
235
270
  chunks.push(chunk);
236
271
  offset += chunkSizeInBytes;
237
272
  assert(
238
- i === chunkCount - 1 || offset <= contentLength,
273
+ chunkId >= chunkCount - 1 || offset <= contentLength,
239
274
  0x58b /* Content offset within bounds */,
240
275
  );
241
276
  }
242
277
 
243
278
  assert(offset >= contentLength, 0x58c /* Content offset equal or larger than content length */);
244
- // The last chunk has empty contents, to minimize the risk of the
245
- // resulting payload exceeding 1MB due to the overhead from the empty ops
246
- // which will be bundled with this op.
247
- chunks.push({
248
- chunkId: chunkCount,
249
- contents: "",
250
- originalType: op.deserializedContent.type,
251
- totalChunks: chunkCount,
252
- originalMetadata: op.metadata,
253
- originalCompression: op.compression,
254
- });
255
-
279
+ assert(chunks.length === chunkCount, 0x5a5 /* Expected number of chunks */);
256
280
  return chunks;
257
281
  };
@@ -15,7 +15,7 @@ import {
15
15
  } from "@fluidframework/telemetry-utils";
16
16
  import { ICompressionRuntimeOptions } from "../containerRuntime";
17
17
  import { PendingStateManager } from "../pendingStateManager";
18
- import { BatchManager } from "./batchManager";
18
+ import { BatchManager, estimateSocketSize } from "./batchManager";
19
19
  import { BatchMessage, IBatch } from "./definitions";
20
20
  import { OpCompressor } from "./opCompressor";
21
21
  import { OpSplitter } from "./opSplitter";
@@ -175,31 +175,35 @@ export class Outbox {
175
175
  if (
176
176
  batch.content.length === 0 ||
177
177
  this.params.config.compressionOptions === undefined ||
178
- this.params.config.compressionOptions.minimumBatchSizeInBytes > batch.contentSizeInBytes
178
+ this.params.config.compressionOptions.minimumBatchSizeInBytes >
179
+ batch.contentSizeInBytes ||
180
+ this.params.containerContext.submitBatchFn === undefined
179
181
  ) {
180
- // Nothing to do if the batch is empty or if compression is disabled or if we don't need to compress
182
+ // Nothing to do if the batch is empty or if compression is disabled or not supported, or if we don't need to compress
181
183
  return batch;
182
184
  }
183
185
 
184
186
  const compressedBatch = this.params.compressor.compressBatch(batch);
185
- if (compressedBatch.contentSizeInBytes <= this.params.config.maxBatchSizeInBytes) {
186
- // If we don't reach the maximum supported size of a batch, it can safely be sent as is
187
- return compressedBatch;
188
- }
189
187
 
190
188
  if (this.params.splitter.isBatchChunkingEnabled) {
191
- return this.params.splitter.splitCompressedBatch(compressedBatch);
189
+ return compressedBatch.contentSizeInBytes <= this.params.splitter.chunkSizeInBytes
190
+ ? compressedBatch
191
+ : this.params.splitter.splitCompressedBatch(compressedBatch);
192
+ }
193
+
194
+ if (compressedBatch.contentSizeInBytes >= this.params.config.maxBatchSizeInBytes) {
195
+ throw new GenericError("BatchTooLarge", /* error */ undefined, {
196
+ batchSize: batch.contentSizeInBytes,
197
+ compressedBatchSize: compressedBatch.contentSizeInBytes,
198
+ count: compressedBatch.content.length,
199
+ limit: this.params.config.maxBatchSizeInBytes,
200
+ chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
201
+ compressionOptions: JSON.stringify(this.params.config.compressionOptions),
202
+ socketSize: estimateSocketSize(batch),
203
+ });
192
204
  }
193
205
 
194
- // If we've reached this point, the runtime would attempt to send a batch larger than the allowed size
195
- throw new GenericError("BatchTooLarge", /* error */ undefined, {
196
- batchSize: batch.contentSizeInBytes,
197
- compressedBatchSize: compressedBatch.contentSizeInBytes,
198
- count: compressedBatch.content.length,
199
- limit: this.params.config.maxBatchSizeInBytes,
200
- chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
201
- compressionOptions: JSON.stringify(this.params.config.compressionOptions),
202
- });
206
+ return compressedBatch;
203
207
  }
204
208
 
205
209
  /**
@@ -216,15 +220,25 @@ export class Outbox {
216
220
  return;
217
221
  }
218
222
 
223
+ const socketSize = estimateSocketSize(batch);
224
+ if (socketSize >= this.params.config.maxBatchSizeInBytes) {
225
+ this.mc.logger.sendPerformanceEvent({
226
+ eventName: "LargeBatch",
227
+ length: batch.content.length,
228
+ sizeInBytes: batch.contentSizeInBytes,
229
+ socketSize,
230
+ });
231
+ }
232
+
219
233
  if (this.params.containerContext.submitBatchFn === undefined) {
220
234
  // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
221
235
  // version that has support for batches (submitBatchFn)
222
- for (const message of batch.content) {
223
- // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
224
- if (message.metadata?.compressed) {
225
- delete message.metadata.compressed;
226
- }
236
+ assert(
237
+ batch.content[0].compression === undefined,
238
+ 0x5a6 /* Compression should not have happened if the loader does not support it */,
239
+ );
227
240
 
241
+ for (const message of batch.content) {
228
242
  this.params.containerContext.submitFn(
229
243
  MessageType.Operation,
230
244
  message.deserializedContent,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.3.2.1";
9
+ export const pkgVersion = "2.0.0-internal.3.3.0";
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ IDocumentStorageService,
8
+ IDocumentStorageServicePolicies,
9
+ } from "@fluidframework/driver-definitions";
10
+ import { DocumentStorageServiceProxy } from "@fluidframework/driver-utils";
11
+
12
+ /**
13
+ * IDocumentStorageService proxy which intercepts requests if they can be satisfied by the blobs received in the
14
+ * attach message. We use this to avoid an unnecessary request to the storage service.
15
+ */
16
+ export class StorageServiceWithAttachBlobs extends DocumentStorageServiceProxy {
17
+ constructor(
18
+ internalStorageService: IDocumentStorageService,
19
+ private readonly attachBlobs: Map<string, ArrayBufferLike>,
20
+ ) {
21
+ super(internalStorageService);
22
+ }
23
+
24
+ public get policies(): IDocumentStorageServicePolicies | undefined {
25
+ return this.internalStorageService.policies;
26
+ }
27
+
28
+ public async readBlob(id: string): Promise<ArrayBufferLike> {
29
+ const blob = this.attachBlobs.get(id);
30
+ if (blob !== undefined) {
31
+ return blob;
32
+ }
33
+
34
+ // Note that it is intentional not to cache the result of this readBlob - we'll trust the real
35
+ // IDocumentStorageService to cache appropriately, no need to double-cache.
36
+ return this.internalStorageService.readBlob(id);
37
+ }
38
+ }
@@ -35,10 +35,7 @@ export class RunWhileConnectedCoordinator implements ICancellableSummarizerContr
35
35
 
36
36
  public get cancelled() {
37
37
  if (!this._cancelled) {
38
- assert(
39
- this.runtime.deltaManager.active,
40
- 0x25d /* "We should never connect as 'read'" */,
41
- );
38
+ assert(this.active(), 0x25d /* "We should never connect as 'read'" */);
42
39
 
43
40
  // This check can't be enabled in current design due to lastSummary flow, where
44
41
  // summarizer for closed container stays around and can produce one more summary.
@@ -63,13 +60,16 @@ export class RunWhileConnectedCoordinator implements ICancellableSummarizerContr
63
60
  return this.stopDeferred.promise;
64
61
  }
65
62
 
66
- public static async create(runtime: IConnectableRuntime) {
67
- const obj = new RunWhileConnectedCoordinator(runtime);
63
+ public static async create(runtime: IConnectableRuntime, active: () => boolean) {
64
+ const obj = new RunWhileConnectedCoordinator(runtime, active);
68
65
  await obj.waitStart();
69
66
  return obj;
70
67
  }
71
68
 
72
- protected constructor(private readonly runtime: IConnectableRuntime) {}
69
+ protected constructor(
70
+ private readonly runtime: IConnectableRuntime,
71
+ private readonly active: () => boolean,
72
+ ) {}
73
73
 
74
74
  /**
75
75
  * Starts and waits for a promise which resolves when connected.
@@ -84,6 +84,7 @@ export interface IConnectableRuntime {
84
84
  readonly disposed: boolean;
85
85
  readonly connected: boolean;
86
86
  readonly clientId: string | undefined;
87
+ /** @deprecated - Moved to `ISummarizerRuntime` as it's no longer needed here */
87
88
  readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
88
89
  once(event: "connected" | "disconnected" | "dispose", listener: () => void): this;
89
90
  }
@@ -92,6 +93,7 @@ export interface ISummarizerRuntime extends IConnectableRuntime {
92
93
  readonly logger: ITelemetryLogger;
93
94
  /** clientId of parent (non-summarizing) container that owns summarizer container */
94
95
  readonly summarizerClientId: string | undefined;
96
+ readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
95
97
  disposeFn?(): void;
96
98
  closeFn(): void;
97
99
  }