@fluidframework/container-runtime 2.32.0 → 2.33.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 (189) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +71 -67
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +7 -4
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +38 -12
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +4 -0
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +24 -0
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/compatUtils.d.ts +74 -0
  13. package/dist/compatUtils.d.ts.map +1 -0
  14. package/dist/compatUtils.js +151 -0
  15. package/dist/compatUtils.js.map +1 -0
  16. package/dist/compressionDefinitions.d.ts +39 -0
  17. package/dist/compressionDefinitions.d.ts.map +1 -0
  18. package/dist/compressionDefinitions.js +30 -0
  19. package/dist/compressionDefinitions.js.map +1 -0
  20. package/dist/containerRuntime.d.ts +78 -52
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +141 -54
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +3 -0
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +122 -66
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/deltaManagerProxies.d.ts +55 -12
  29. package/dist/deltaManagerProxies.d.ts.map +1 -1
  30. package/dist/deltaManagerProxies.js +63 -55
  31. package/dist/deltaManagerProxies.js.map +1 -1
  32. package/dist/gc/gcDefinitions.d.ts +2 -0
  33. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  34. package/dist/gc/gcDefinitions.js.map +1 -1
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/legacy.d.ts +1 -0
  40. package/dist/opLifecycle/batchManager.d.ts +3 -15
  41. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  42. package/dist/opLifecycle/batchManager.js +5 -39
  43. package/dist/opLifecycle/batchManager.js.map +1 -1
  44. package/dist/opLifecycle/definitions.d.ts +44 -11
  45. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  46. package/dist/opLifecycle/definitions.js.map +1 -1
  47. package/dist/opLifecycle/index.d.ts +3 -3
  48. package/dist/opLifecycle/index.d.ts.map +1 -1
  49. package/dist/opLifecycle/index.js +3 -2
  50. package/dist/opLifecycle/index.js.map +1 -1
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  52. package/dist/opLifecycle/opCompressor.js +4 -4
  53. package/dist/opLifecycle/opCompressor.js.map +1 -1
  54. package/dist/opLifecycle/opDecompressor.js +3 -3
  55. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  56. package/dist/opLifecycle/opGroupingManager.d.ts +2 -2
  57. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  58. package/dist/opLifecycle/opGroupingManager.js +1 -2
  59. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  60. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  61. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  62. package/dist/opLifecycle/opSerialization.js +4 -2
  63. package/dist/opLifecycle/opSerialization.js.map +1 -1
  64. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  65. package/dist/opLifecycle/opSplitter.js +2 -2
  66. package/dist/opLifecycle/opSplitter.js.map +1 -1
  67. package/dist/opLifecycle/outbox.d.ts +25 -3
  68. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  69. package/dist/opLifecycle/outbox.js +112 -61
  70. package/dist/opLifecycle/outbox.js.map +1 -1
  71. package/dist/packageVersion.d.ts +1 -1
  72. package/dist/packageVersion.js +1 -1
  73. package/dist/packageVersion.js.map +1 -1
  74. package/dist/pendingStateManager.d.ts +36 -7
  75. package/dist/pendingStateManager.d.ts.map +1 -1
  76. package/dist/pendingStateManager.js +83 -16
  77. package/dist/pendingStateManager.js.map +1 -1
  78. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  79. package/dist/runtimeLayerCompatState.js +1 -1
  80. package/dist/runtimeLayerCompatState.js.map +1 -1
  81. package/dist/summary/documentSchema.d.ts +1 -0
  82. package/dist/summary/documentSchema.d.ts.map +1 -1
  83. package/dist/summary/documentSchema.js +2 -0
  84. package/dist/summary/documentSchema.js.map +1 -1
  85. package/lib/blobManager/blobManager.d.ts +7 -4
  86. package/lib/blobManager/blobManager.d.ts.map +1 -1
  87. package/lib/blobManager/blobManager.js +38 -12
  88. package/lib/blobManager/blobManager.js.map +1 -1
  89. package/lib/channelCollection.d.ts +4 -0
  90. package/lib/channelCollection.d.ts.map +1 -1
  91. package/lib/channelCollection.js +24 -0
  92. package/lib/channelCollection.js.map +1 -1
  93. package/lib/compatUtils.d.ts +74 -0
  94. package/lib/compatUtils.d.ts.map +1 -0
  95. package/lib/compatUtils.js +142 -0
  96. package/lib/compatUtils.js.map +1 -0
  97. package/lib/compressionDefinitions.d.ts +39 -0
  98. package/lib/compressionDefinitions.d.ts.map +1 -0
  99. package/lib/compressionDefinitions.js +27 -0
  100. package/lib/compressionDefinitions.js.map +1 -0
  101. package/lib/containerRuntime.d.ts +78 -52
  102. package/lib/containerRuntime.d.ts.map +1 -1
  103. package/lib/containerRuntime.js +143 -56
  104. package/lib/containerRuntime.js.map +1 -1
  105. package/lib/dataStoreContext.d.ts +3 -0
  106. package/lib/dataStoreContext.d.ts.map +1 -1
  107. package/lib/dataStoreContext.js +57 -1
  108. package/lib/dataStoreContext.js.map +1 -1
  109. package/lib/deltaManagerProxies.d.ts +55 -12
  110. package/lib/deltaManagerProxies.d.ts.map +1 -1
  111. package/lib/deltaManagerProxies.js +63 -55
  112. package/lib/deltaManagerProxies.js.map +1 -1
  113. package/lib/gc/gcDefinitions.d.ts +2 -0
  114. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  115. package/lib/gc/gcDefinitions.js.map +1 -1
  116. package/lib/index.d.ts +4 -2
  117. package/lib/index.d.ts.map +1 -1
  118. package/lib/index.js +2 -1
  119. package/lib/index.js.map +1 -1
  120. package/lib/legacy.d.ts +1 -0
  121. package/lib/opLifecycle/batchManager.d.ts +3 -15
  122. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  123. package/lib/opLifecycle/batchManager.js +4 -37
  124. package/lib/opLifecycle/batchManager.js.map +1 -1
  125. package/lib/opLifecycle/definitions.d.ts +44 -11
  126. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  127. package/lib/opLifecycle/definitions.js.map +1 -1
  128. package/lib/opLifecycle/index.d.ts +3 -3
  129. package/lib/opLifecycle/index.d.ts.map +1 -1
  130. package/lib/opLifecycle/index.js +2 -2
  131. package/lib/opLifecycle/index.js.map +1 -1
  132. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  133. package/lib/opLifecycle/opCompressor.js +2 -2
  134. package/lib/opLifecycle/opCompressor.js.map +1 -1
  135. package/lib/opLifecycle/opDecompressor.js +1 -1
  136. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  137. package/lib/opLifecycle/opGroupingManager.d.ts +2 -2
  138. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  139. package/lib/opLifecycle/opGroupingManager.js +1 -2
  140. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  141. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  142. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSerialization.js +4 -2
  144. package/lib/opLifecycle/opSerialization.js.map +1 -1
  145. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  146. package/lib/opLifecycle/opSplitter.js +1 -1
  147. package/lib/opLifecycle/opSplitter.js.map +1 -1
  148. package/lib/opLifecycle/outbox.d.ts +25 -3
  149. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  150. package/lib/opLifecycle/outbox.js +110 -61
  151. package/lib/opLifecycle/outbox.js.map +1 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.js +1 -1
  154. package/lib/packageVersion.js.map +1 -1
  155. package/lib/pendingStateManager.d.ts +36 -7
  156. package/lib/pendingStateManager.d.ts.map +1 -1
  157. package/lib/pendingStateManager.js +84 -17
  158. package/lib/pendingStateManager.js.map +1 -1
  159. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  160. package/lib/runtimeLayerCompatState.js +2 -2
  161. package/lib/runtimeLayerCompatState.js.map +1 -1
  162. package/lib/summary/documentSchema.d.ts +1 -0
  163. package/lib/summary/documentSchema.d.ts.map +1 -1
  164. package/lib/summary/documentSchema.js +2 -0
  165. package/lib/summary/documentSchema.js.map +1 -1
  166. package/lib/tsdoc-metadata.json +1 -1
  167. package/package.json +21 -20
  168. package/src/blobManager/blobManager.ts +48 -15
  169. package/src/channelCollection.ts +27 -0
  170. package/src/compatUtils.ts +211 -0
  171. package/src/compressionDefinitions.ts +47 -0
  172. package/src/containerRuntime.ts +259 -108
  173. package/src/dataStoreContext.ts +82 -2
  174. package/src/deltaManagerProxies.ts +132 -70
  175. package/src/gc/gcDefinitions.ts +2 -0
  176. package/src/index.ts +5 -3
  177. package/src/opLifecycle/batchManager.ts +7 -52
  178. package/src/opLifecycle/definitions.ts +45 -11
  179. package/src/opLifecycle/index.ts +7 -2
  180. package/src/opLifecycle/opCompressor.ts +2 -2
  181. package/src/opLifecycle/opDecompressor.ts +1 -1
  182. package/src/opLifecycle/opGroupingManager.ts +7 -5
  183. package/src/opLifecycle/opSerialization.ts +6 -2
  184. package/src/opLifecycle/opSplitter.ts +1 -1
  185. package/src/opLifecycle/outbox.ts +154 -85
  186. package/src/packageVersion.ts +1 -1
  187. package/src/pendingStateManager.ts +135 -21
  188. package/src/runtimeLayerCompatState.ts +5 -2
  189. package/src/summary/documentSchema.ts +3 -0
@@ -7,7 +7,6 @@ export {
7
7
  BatchId,
8
8
  BatchManager,
9
9
  BatchSequenceNumbers,
10
- estimateSocketSize,
11
10
  getEffectiveBatchId,
12
11
  generateBatchId,
13
12
  IBatchManagerOptions,
@@ -15,6 +14,7 @@ export {
15
14
  export {
16
15
  LocalBatch,
17
16
  LocalBatchMessage,
17
+ LocalEmptyBatchPlaceholder,
18
18
  OutboundBatch,
19
19
  OutboundBatchMessage,
20
20
  OutboundSingletonBatch,
@@ -26,7 +26,12 @@ export {
26
26
  serializeOp,
27
27
  ensureContentsDeserialized,
28
28
  } from "./opSerialization.js";
29
- export { Outbox, getLongStack } from "./outbox.js";
29
+ export {
30
+ estimateSocketSize,
31
+ localBatchToOutboundBatch,
32
+ Outbox,
33
+ getLongStack,
34
+ } from "./outbox.js";
30
35
  export { OpCompressor } from "./opCompressor.js";
31
36
  export { OpDecompressor } from "./opDecompressor.js";
32
37
  export { OpSplitter, splitOp, isChunkedMessage } from "./opSplitter.js";
@@ -13,10 +13,10 @@ import {
13
13
  } from "@fluidframework/telemetry-utils/internal";
14
14
  import { compress } from "lz4js";
15
15
 
16
- import { CompressionAlgorithms } from "../containerRuntime.js";
16
+ import { CompressionAlgorithms } from "../compressionDefinitions.js";
17
17
 
18
- import { estimateSocketSize } from "./batchManager.js";
19
18
  import { type OutboundBatchMessage, type OutboundSingletonBatch } from "./definitions.js";
19
+ import { estimateSocketSize } from "./outbox.js";
20
20
 
21
21
  /**
22
22
  * Compresses batches of ops.
@@ -13,7 +13,7 @@ import {
13
13
  } from "@fluidframework/telemetry-utils/internal";
14
14
  import { decompress } from "lz4js";
15
15
 
16
- import { CompressionAlgorithms } from "../containerRuntime.js";
16
+ import { CompressionAlgorithms } from "../compressionDefinitions.js";
17
17
  import { IBatchMetadata } from "../metadata.js";
18
18
 
19
19
  /**
@@ -12,8 +12,8 @@ import {
12
12
  } from "@fluidframework/telemetry-utils/internal";
13
13
 
14
14
  import {
15
+ type LocalEmptyBatchPlaceholder,
15
16
  type OutboundBatch,
16
- type OutboundBatchMessage,
17
17
  type OutboundSingletonBatch,
18
18
  } from "./definitions.js";
19
19
 
@@ -67,7 +67,10 @@ export class OpGroupingManager {
67
67
  public createEmptyGroupedBatch(
68
68
  resubmittingBatchId: string,
69
69
  referenceSequenceNumber: number,
70
- ): { outboundBatch: OutboundSingletonBatch; placeholderMessage: OutboundBatchMessage } {
70
+ ): {
71
+ outboundBatch: OutboundSingletonBatch;
72
+ placeholderMessage: LocalEmptyBatchPlaceholder;
73
+ } {
71
74
  assert(
72
75
  this.config.groupedBatchingEnabled,
73
76
  0xa00 /* cannot create empty grouped batch when grouped batching is disabled */,
@@ -77,15 +80,14 @@ export class OpGroupingManager {
77
80
  contents: [],
78
81
  });
79
82
 
80
- const placeholderMessage: OutboundBatchMessage = {
83
+ const placeholderMessage: LocalEmptyBatchPlaceholder = {
81
84
  metadata: { batchId: resubmittingBatchId },
82
85
  localOpMetadata: { emptyBatch: true },
83
86
  referenceSequenceNumber,
84
- contents: serializedOp,
85
87
  };
86
88
  const outboundBatch: OutboundSingletonBatch = {
87
89
  contentSizeInBytes: 0,
88
- messages: [placeholderMessage],
90
+ messages: [{ ...placeholderMessage, contents: serializedOp }],
89
91
  referenceSequenceNumber,
90
92
  };
91
93
  return { outboundBatch, placeholderMessage };
@@ -30,10 +30,14 @@ export function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMes
30
30
  /**
31
31
  * Before submitting an op to the Outbox, its contents must be serialized using this function.
32
32
  * @remarks - The deserialization on process happens via the function {@link ensureContentsDeserialized}.
33
+ *
34
+ * @param toSerialize - op message to serialize. Also supports an array of ops.
33
35
  */
34
- export function serializeOp(op: LocalContainerRuntimeMessage): string {
36
+ export function serializeOp(
37
+ toSerialize: LocalContainerRuntimeMessage | LocalContainerRuntimeMessage[],
38
+ ): string {
35
39
  return JSON.stringify(
36
- op,
40
+ toSerialize,
37
41
  // replacer:
38
42
  (key, value: unknown) => {
39
43
  // If 'value' is an IFluidHandle return its encoded form.
@@ -16,12 +16,12 @@ import {
16
16
 
17
17
  import { ContainerMessageType, ContainerRuntimeChunkedOpMessage } from "../messageTypes.js";
18
18
 
19
- import { estimateSocketSize } from "./batchManager.js";
20
19
  import {
21
20
  IChunkedOp,
22
21
  type OutboundBatchMessage,
23
22
  type OutboundSingletonBatch,
24
23
  } from "./definitions.js";
24
+ import { estimateSocketSize } from "./outbox.js";
25
25
 
26
26
  export function isChunkedMessage(message: ISequencedDocumentMessage): boolean {
27
27
  return isChunkedContents(message.contents);
@@ -4,23 +4,25 @@
4
4
  */
5
5
 
6
6
  import { IBatchMessage } from "@fluidframework/container-definitions/internal";
7
- import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
+ import {
8
+ ITelemetryBaseLogger,
9
+ type ITelemetryBaseProperties,
10
+ } from "@fluidframework/core-interfaces";
8
11
  import { assert, Lazy } from "@fluidframework/core-utils/internal";
9
12
  import {
10
13
  DataProcessingError,
11
- GenericError,
12
14
  UsageError,
13
15
  createChildLogger,
16
+ type IFluidErrorBase,
14
17
  type ITelemetryLoggerExt,
15
18
  } from "@fluidframework/telemetry-utils/internal";
16
19
 
17
- import { ICompressionRuntimeOptions } from "../containerRuntime.js";
20
+ import { ICompressionRuntimeOptions } from "../compressionDefinitions.js";
18
21
  import { PendingMessageResubmitData, PendingStateManager } from "../pendingStateManager.js";
19
22
 
20
23
  import {
21
24
  BatchManager,
22
25
  BatchSequenceNumbers,
23
- estimateSocketSize,
24
26
  sequenceNumbersMatch,
25
27
  type BatchId,
26
28
  } from "./batchManager.js";
@@ -34,6 +36,7 @@ import {
34
36
  } from "./definitions.js";
35
37
  import { OpCompressor } from "./opCompressor.js";
36
38
  import { OpGroupingManager } from "./opGroupingManager.js";
39
+ import { serializeOp } from "./opSerialization.js";
37
40
  import { OpSplitter } from "./opSplitter.js";
38
41
 
39
42
  export interface IOutboxConfig {
@@ -106,6 +109,61 @@ export function getLongStack<T>(action: () => T, length: number = 50): T {
106
109
  }
107
110
  }
108
111
 
112
+ /**
113
+ * Convert from local batch to outbound batch, including computing contentSizeInBytes.
114
+ */
115
+ export function localBatchToOutboundBatch({
116
+ staged: _staged, // Peel this off the incoming batch, it's irrelevant (see Note below)
117
+ ...localBatch
118
+ }: LocalBatch): OutboundBatch {
119
+ // Note: the staged property might be misleading here, in case a pre-staging batch is resubmitted during staging mode.
120
+ // It will be set to true, but the batch was not actually staged.
121
+
122
+ // Shallow copy each message as we switch types
123
+ const outboundMessages = localBatch.messages.map<OutboundBatchMessage>(
124
+ ({ runtimeOp, ...message }) => ({
125
+ contents: serializeOp(runtimeOp),
126
+ ...message,
127
+ }),
128
+ );
129
+ const contentSizeInBytes = outboundMessages.reduce(
130
+ (acc, message) => acc + (message.contents?.length ?? 0),
131
+ 0,
132
+ );
133
+
134
+ // Shallow copy the local batch, updating the messages to be outbound messages and adding contentSizeInBytes
135
+ const outboundBatch: OutboundBatch = {
136
+ ...localBatch,
137
+ messages: outboundMessages,
138
+ contentSizeInBytes,
139
+ };
140
+
141
+ return outboundBatch;
142
+ }
143
+
144
+ /**
145
+ * Estimated size of the stringification overhead for an op accumulated
146
+ * from runtime to loader to the service.
147
+ */
148
+ const opOverhead = 200;
149
+
150
+ /**
151
+ * Estimates the real size in bytes on the socket for a given batch. It assumes that
152
+ * the envelope size (and the size of an empty op) is 200 bytes, taking into account
153
+ * extra overhead from stringification.
154
+ *
155
+ * @remarks
156
+ * Also content will be stringified, and that adds a lot of overhead due to a lot of escape characters.
157
+ * Not taking it into account, as compression work should help there - compressed payload will be
158
+ * initially stored as base64, and that requires only 2 extra escape characters.
159
+ *
160
+ * @param batch - the batch to inspect
161
+ * @returns An estimate of the payload size in bytes which will be produced when the batch is sent over the wire
162
+ */
163
+ export const estimateSocketSize = (batch: OutboundBatch): number => {
164
+ return batch.contentSizeInBytes + opOverhead * batch.messages.length;
165
+ };
166
+
109
167
  /**
110
168
  * The Outbox collects messages submitted by the ContainerRuntime into a batch,
111
169
  * and then flushes the batch when requested.
@@ -133,18 +191,9 @@ export class Outbox {
133
191
  constructor(private readonly params: IOutboxParameters) {
134
192
  this.logger = createChildLogger({ logger: params.logger, namespace: "Outbox" });
135
193
 
136
- const isCompressionEnabled =
137
- this.params.config.compressionOptions.minimumBatchSizeInBytes !==
138
- Number.POSITIVE_INFINITY;
139
- // We need to allow infinite size batches if we enable compression
140
- const hardLimit = isCompressionEnabled
141
- ? Number.POSITIVE_INFINITY
142
- : this.params.config.maxBatchSizeInBytes;
143
-
144
- this.mainBatch = new BatchManager({ hardLimit, canRebase: true });
145
- this.blobAttachBatch = new BatchManager({ hardLimit, canRebase: true });
194
+ this.mainBatch = new BatchManager({ canRebase: true });
195
+ this.blobAttachBatch = new BatchManager({ canRebase: true });
146
196
  this.idAllocationBatch = new BatchManager({
147
- hardLimit,
148
197
  canRebase: false,
149
198
  ignoreBatchId: true,
150
199
  });
@@ -224,7 +273,7 @@ export class Outbox {
224
273
  ? "generic"
225
274
  : "error",
226
275
  eventName: "ReferenceSequenceNumberMismatch",
227
- Data_details: {
276
+ details: {
228
277
  expectedDueToReentrancy,
229
278
  mainReferenceSequenceNumber: mainBatchSeqNums.referenceSequenceNumber,
230
279
  mainClientSequenceNumber: mainBatchSeqNums.clientSequenceNumber,
@@ -274,20 +323,11 @@ export class Outbox {
274
323
  batchManager: BatchManager,
275
324
  message: LocalBatchMessage,
276
325
  ): void {
277
- if (
278
- !batchManager.push(
279
- message,
280
- this.isContextReentrant(),
281
- this.params.getCurrentSequenceNumbers().clientSequenceNumber,
282
- )
283
- ) {
284
- throw new GenericError("BatchTooLarge", /* error */ undefined, {
285
- opSize: message.serializedOp?.length ?? 0,
286
- batchSize: batchManager.contentSizeInBytes,
287
- count: batchManager.length,
288
- limit: batchManager.options.hardLimit,
289
- });
290
- }
326
+ batchManager.push(
327
+ message,
328
+ this.isContextReentrant(),
329
+ this.params.getCurrentSequenceNumbers().clientSequenceNumber,
330
+ );
291
331
  }
292
332
 
293
333
  /**
@@ -297,17 +337,19 @@ export class Outbox {
297
337
  * @throws If called from a reentrant context, or if the batch being flushed is too large.
298
338
  * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
299
339
  * with the given Batch ID, which must be preserved
340
+ * @param resubmittingStagedBatch - If defined, indicates this is a resubmission of a batch that is staged,
341
+ * meaning it should not be sent to the ordering service yet.
300
342
  */
301
- public flush(resubmittingBatchId?: BatchId): void {
343
+ public flush(resubmittingBatchId?: BatchId, resubmittingStagedBatch?: boolean): void {
302
344
  assert(
303
345
  !this.isContextReentrant(),
304
346
  0xb7b /* Flushing must not happen while incoming changes are being processed */,
305
347
  );
306
348
 
307
- this.flushAll(resubmittingBatchId);
349
+ this.flushAll(resubmittingBatchId, resubmittingStagedBatch);
308
350
  }
309
351
 
310
- private flushAll(resubmittingBatchId?: BatchId): void {
352
+ private flushAll(resubmittingBatchId?: BatchId, resubmittingStagedBatch?: boolean): void {
311
353
  const allBatchesEmpty =
312
354
  this.idAllocationBatch.empty && this.blobAttachBatch.empty && this.mainBatch.empty;
313
355
  if (allBatchesEmpty) {
@@ -317,27 +359,35 @@ export class Outbox {
317
359
  // by the rest of the system, including remote clients.
318
360
  // In some cases we *must* resubmit the empty batch (to match up with a non-empty version tracked locally by a container fork), so we do it always.
319
361
  if (resubmittingBatchId) {
320
- this.flushEmptyBatch(resubmittingBatchId);
362
+ this.flushEmptyBatch(resubmittingBatchId, resubmittingStagedBatch === true);
321
363
  }
322
364
  return;
323
365
  }
324
366
 
325
367
  // Don't use resubmittingBatchId for idAllocationBatch.
326
368
  // ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
327
- this.flushInternal(this.idAllocationBatch);
328
- this.flushInternal(
329
- this.blobAttachBatch,
330
- true /* disableGroupedBatching */,
369
+ this.flushInternal({
370
+ batchManager: this.idAllocationBatch,
371
+ // Note: For now, we will never stage ID Allocation messages.
372
+ // They won't contain personal info and no harm in extra allocations in case of discarding the staged changes
373
+ });
374
+ this.flushInternal({
375
+ batchManager: this.blobAttachBatch,
376
+ disableGroupedBatching: true,
331
377
  resubmittingBatchId,
332
- );
333
- this.flushInternal(
334
- this.mainBatch,
335
- false /* disableGroupedBatching */,
378
+ resubmittingStagedBatch,
379
+ });
380
+ this.flushInternal({
381
+ batchManager: this.mainBatch,
336
382
  resubmittingBatchId,
337
- );
383
+ resubmittingStagedBatch,
384
+ });
338
385
  }
339
386
 
340
- private flushEmptyBatch(resubmittingBatchId: BatchId): void {
387
+ private flushEmptyBatch(
388
+ resubmittingBatchId: BatchId,
389
+ resubmittingStagedBatch: boolean,
390
+ ): void {
341
391
  const referenceSequenceNumber =
342
392
  this.params.getCurrentSequenceNumbers().referenceSequenceNumber;
343
393
  assert(
@@ -350,28 +400,42 @@ export class Outbox {
350
400
  referenceSequenceNumber,
351
401
  );
352
402
  let clientSequenceNumber: number | undefined;
353
- if (this.params.shouldSend()) {
403
+ if (this.params.shouldSend() && !resubmittingStagedBatch) {
354
404
  clientSequenceNumber = this.sendBatch(outboundBatch);
355
405
  }
356
406
 
357
407
  // Push the empty batch placeholder to the PendingStateManager
358
- this.params.pendingStateManager.onFlushBatch(
359
- [{ ...placeholderMessage, serializedOp: "", contents: undefined }], // placeholder message - serializedOp will never be used
408
+ this.params.pendingStateManager.onFlushEmptyBatch(
409
+ placeholderMessage,
360
410
  clientSequenceNumber,
411
+ resubmittingStagedBatch,
361
412
  );
362
413
  return;
363
414
  }
364
415
 
365
- private flushInternal(
366
- batchManager: BatchManager,
367
- disableGroupedBatching: boolean = false,
368
- resubmittingBatchId?: BatchId,
369
- ): void {
416
+ private flushInternal(params: {
417
+ batchManager: BatchManager;
418
+ disableGroupedBatching?: boolean;
419
+ resubmittingBatchId?: BatchId; // undefined if not resubmitting
420
+ resubmittingStagedBatch?: boolean; // undefined if not resubmitting
421
+ }): void {
422
+ const {
423
+ batchManager,
424
+ disableGroupedBatching = false,
425
+ resubmittingBatchId,
426
+ resubmittingStagedBatch,
427
+ } = params;
370
428
  if (batchManager.empty) {
371
429
  return;
372
430
  }
373
431
 
374
432
  const rawBatch = batchManager.popBatch(resubmittingBatchId);
433
+
434
+ // When resubmitting, we respect the staged state of the original batch.
435
+ // In this case rawBatch.staged will match the state of inStagingMode when
436
+ // the resubmit occurred, which is not relevant.
437
+ const staged = resubmittingStagedBatch ?? rawBatch.staged === true;
438
+
375
439
  const groupingEnabled =
376
440
  !disableGroupedBatching && this.params.groupingManager.groupedBatchingEnabled();
377
441
  if (
@@ -386,6 +450,9 @@ export class Outbox {
386
450
  // If a batch contains reentrant ops (ops created as a result from processing another op)
387
451
  // it needs to be rebased so that we can ensure consistent reference sequence numbers
388
452
  // and eventual consistency at the DDS level.
453
+ // Note: Since this is happening in the same turn the ops were originally created with,
454
+ // and they haven't gone to PendingStateManager yet, we can just let them respect
455
+ // ContainerRuntime.inStagingMode
389
456
  this.rebase(rawBatch, batchManager);
390
457
  return;
391
458
  }
@@ -394,7 +461,7 @@ export class Outbox {
394
461
  // Did we disconnect? (i.e. is shouldSend false?)
395
462
  // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
396
463
  // Because flush() is a task that executes async (on clean stack), we can get here in disconnected state.
397
- if (this.params.shouldSend()) {
464
+ if (this.params.shouldSend() && !staged) {
398
465
  const virtualizedBatch = this.virtualizeBatch(rawBatch, groupingEnabled);
399
466
 
400
467
  clientSequenceNumber = this.sendBatch(virtualizedBatch);
@@ -407,6 +474,7 @@ export class Outbox {
407
474
  this.params.pendingStateManager.onFlushBatch(
408
475
  rawBatch.messages,
409
476
  clientSequenceNumber,
477
+ staged,
410
478
  batchManager.options.ignoreBatchId,
411
479
  );
412
480
  }
@@ -424,7 +492,7 @@ export class Outbox {
424
492
  this.rebasing = true;
425
493
  for (const message of rawBatch.messages) {
426
494
  this.params.reSubmit({
427
- content: message.serializedOp,
495
+ runtimeOp: message.runtimeOp,
428
496
  localOpMetadata: message.localOpMetadata,
429
497
  opMetadata: message.metadata,
430
498
  });
@@ -442,7 +510,7 @@ export class Outbox {
442
510
  this.batchRebasesToReport--;
443
511
  }
444
512
 
445
- this.flushInternal(batchManager);
513
+ this.flushInternal({ batchManager });
446
514
  this.rebasing = false;
447
515
  }
448
516
 
@@ -465,15 +533,7 @@ export class Outbox {
465
533
  */
466
534
  private virtualizeBatch(localBatch: LocalBatch, groupingEnabled: boolean): OutboundBatch {
467
535
  // Shallow copy the local batch, updating the messages to be outbound messages
468
- const originalBatch: OutboundBatch = {
469
- ...localBatch,
470
- messages: localBatch.messages.map<OutboundBatchMessage>(
471
- ({ serializedOp, ...message }) => ({
472
- contents: serializedOp,
473
- ...message,
474
- }),
475
- ),
476
- };
536
+ const originalBatch = localBatchToOutboundBatch(localBatch);
477
537
 
478
538
  const originalOrGroupedBatch = groupingEnabled
479
539
  ? this.params.groupingManager.groupBatch(originalBatch)
@@ -489,7 +549,6 @@ export class Outbox {
489
549
  const singletonBatch = originalOrGroupedBatch as OutboundSingletonBatch;
490
550
 
491
551
  if (
492
- this.params.config.compressionOptions === undefined ||
493
552
  this.params.config.compressionOptions.minimumBatchSizeInBytes >
494
553
  singletonBatch.contentSizeInBytes ||
495
554
  this.params.submitBatchFn === undefined
@@ -506,21 +565,11 @@ export class Outbox {
506
565
  : this.params.splitter.splitSingletonBatchMessage(compressedBatch);
507
566
  }
508
567
 
568
+ // We want to distinguish this "BatchTooLarge" case from the generic "BatchTooLarge" case in sendBatch
509
569
  if (compressedBatch.contentSizeInBytes >= this.params.config.maxBatchSizeInBytes) {
510
- throw DataProcessingError.create(
511
- "BatchTooLarge",
512
- "compressionInsufficient",
513
- /* sequencedMessage */ undefined,
514
- {
515
- batchSize: singletonBatch.contentSizeInBytes,
516
- compressedBatchSize: compressedBatch.contentSizeInBytes,
517
- count: compressedBatch.messages.length,
518
- limit: this.params.config.maxBatchSizeInBytes,
519
- chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
520
- compressionOptions: JSON.stringify(this.params.config.compressionOptions),
521
- socketSize: estimateSocketSize(singletonBatch),
522
- },
523
- );
570
+ throw this.makeBatchTooLargeError(compressedBatch, "CompressionInsufficient", {
571
+ uncompressedSizeInBytes: singletonBatch.contentSizeInBytes,
572
+ });
524
573
  }
525
574
 
526
575
  return compressedBatch;
@@ -540,12 +589,7 @@ export class Outbox {
540
589
 
541
590
  const socketSize = estimateSocketSize(batch);
542
591
  if (socketSize >= this.params.config.maxBatchSizeInBytes) {
543
- this.logger.sendPerformanceEvent({
544
- eventName: "LargeBatch",
545
- length: batch.messages.length,
546
- sizeInBytes: batch.contentSizeInBytes,
547
- socketSize,
548
- });
592
+ throw this.makeBatchTooLargeError(batch, "CannotSend");
549
593
  }
550
594
 
551
595
  let clientSequenceNumber: number;
@@ -577,6 +621,31 @@ export class Outbox {
577
621
  return clientSequenceNumber;
578
622
  }
579
623
 
624
+ private makeBatchTooLargeError(
625
+ batch: OutboundBatch,
626
+ codepath: string,
627
+ moreDetails?: ITelemetryBaseProperties,
628
+ ): IFluidErrorBase {
629
+ return DataProcessingError.create(
630
+ "BatchTooLarge",
631
+ codepath,
632
+ /* sequencedMessage */ undefined,
633
+ {
634
+ errorDetails: {
635
+ opCount: batch.messages.length,
636
+ contentSizeInBytes: batch.contentSizeInBytes,
637
+ socketSize: estimateSocketSize(batch),
638
+ maxBatchSizeInBytes: this.params.config.maxBatchSizeInBytes,
639
+ groupedBatchingEnabled: this.params.groupingManager.groupedBatchingEnabled(),
640
+ compressionOptions: JSON.stringify(this.params.config.compressionOptions),
641
+ chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
642
+ chunkSizeInBytes: this.params.splitter.chunkSizeInBytes,
643
+ ...moreDetails,
644
+ },
645
+ },
646
+ );
647
+ }
648
+
580
649
  /**
581
650
  * Gets a checkpoint object per batch that facilitates iterating over the batch messages when rolling back.
582
651
  */
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.32.0";
9
+ export const pkgVersion = "2.33.0";