@fluidframework/container-runtime 2.1.0-276326 → 2.1.0-281041

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 (236) hide show
  1. package/README.md +74 -21
  2. package/api-extractor/api-extractor.current.json +5 -0
  3. package/api-extractor/api-extractor.legacy.json +1 -1
  4. package/api-extractor.json +1 -1
  5. package/api-report/container-runtime.legacy.public.api.md +9 -0
  6. package/container-runtime.test-files.tar +0 -0
  7. package/dist/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  8. package/dist/blobManager/blobManager.d.ts.map +1 -0
  9. package/dist/{blobManager.js → blobManager/blobManager.js} +42 -83
  10. package/dist/blobManager/blobManager.js.map +1 -0
  11. package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
  12. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  13. package/dist/blobManager/blobManagerSnapSum.js +82 -0
  14. package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
  15. package/dist/blobManager/index.d.ts +7 -0
  16. package/dist/blobManager/index.d.ts.map +1 -0
  17. package/dist/blobManager/index.js +16 -0
  18. package/dist/blobManager/index.js.map +1 -0
  19. package/dist/channelCollection.d.ts +1 -1
  20. package/dist/channelCollection.d.ts.map +1 -1
  21. package/dist/channelCollection.js +40 -8
  22. package/dist/channelCollection.js.map +1 -1
  23. package/dist/containerRuntime.d.ts +15 -10
  24. package/dist/containerRuntime.d.ts.map +1 -1
  25. package/dist/containerRuntime.js +199 -162
  26. package/dist/containerRuntime.js.map +1 -1
  27. package/dist/dataStoreContext.d.ts +5 -0
  28. package/dist/dataStoreContext.d.ts.map +1 -1
  29. package/dist/dataStoreContext.js +16 -5
  30. package/dist/dataStoreContext.js.map +1 -1
  31. package/dist/gc/garbageCollection.d.ts +1 -1
  32. package/dist/gc/garbageCollection.d.ts.map +1 -1
  33. package/dist/gc/garbageCollection.js +16 -10
  34. package/dist/gc/garbageCollection.js.map +1 -1
  35. package/dist/gc/gcDefinitions.d.ts +4 -2
  36. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  37. package/dist/gc/gcDefinitions.js.map +1 -1
  38. package/dist/gc/gcHelpers.d.ts.map +1 -1
  39. package/dist/gc/gcHelpers.js +12 -0
  40. package/dist/gc/gcHelpers.js.map +1 -1
  41. package/dist/gc/gcTelemetry.d.ts +3 -2
  42. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  43. package/dist/gc/gcTelemetry.js +6 -6
  44. package/dist/gc/gcTelemetry.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/legacy.d.ts +1 -1
  49. package/dist/metadata.d.ts +7 -1
  50. package/dist/metadata.d.ts.map +1 -1
  51. package/dist/metadata.js +6 -0
  52. package/dist/metadata.js.map +1 -1
  53. package/dist/opLifecycle/batchManager.d.ts +8 -1
  54. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  55. package/dist/opLifecycle/batchManager.js +37 -16
  56. package/dist/opLifecycle/batchManager.js.map +1 -1
  57. package/dist/opLifecycle/definitions.d.ts +1 -1
  58. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  59. package/dist/opLifecycle/definitions.js.map +1 -1
  60. package/dist/opLifecycle/index.d.ts +1 -1
  61. package/dist/opLifecycle/index.d.ts.map +1 -1
  62. package/dist/opLifecycle/index.js +2 -1
  63. package/dist/opLifecycle/index.js.map +1 -1
  64. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  65. package/dist/opLifecycle/opCompressor.js +12 -8
  66. package/dist/opLifecycle/opCompressor.js.map +1 -1
  67. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  68. package/dist/opLifecycle/opGroupingManager.js +14 -11
  69. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  70. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  71. package/dist/opLifecycle/opSplitter.js +11 -6
  72. package/dist/opLifecycle/opSplitter.js.map +1 -1
  73. package/dist/opLifecycle/outbox.d.ts +22 -6
  74. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  75. package/dist/opLifecycle/outbox.js +43 -21
  76. package/dist/opLifecycle/outbox.js.map +1 -1
  77. package/dist/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  78. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  79. package/dist/opLifecycle/remoteMessageProcessor.js +59 -9
  80. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  81. package/dist/packageVersion.d.ts +1 -1
  82. package/dist/packageVersion.js +1 -1
  83. package/dist/packageVersion.js.map +1 -1
  84. package/dist/pendingStateManager.d.ts +39 -13
  85. package/dist/pendingStateManager.d.ts.map +1 -1
  86. package/dist/pendingStateManager.js +98 -33
  87. package/dist/pendingStateManager.js.map +1 -1
  88. package/dist/public.d.ts +1 -1
  89. package/dist/scheduleManager.js +4 -0
  90. package/dist/scheduleManager.js.map +1 -1
  91. package/dist/summary/index.d.ts +1 -1
  92. package/dist/summary/index.d.ts.map +1 -1
  93. package/dist/summary/index.js +1 -2
  94. package/dist/summary/index.js.map +1 -1
  95. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  96. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  97. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  98. package/dist/summary/summaryFormat.d.ts +0 -1
  99. package/dist/summary/summaryFormat.d.ts.map +1 -1
  100. package/dist/summary/summaryFormat.js +7 -4
  101. package/dist/summary/summaryFormat.js.map +1 -1
  102. package/internal.d.ts +1 -1
  103. package/legacy.d.ts +1 -1
  104. package/lib/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  105. package/lib/blobManager/blobManager.d.ts.map +1 -0
  106. package/lib/{blobManager.js → blobManager/blobManager.js} +40 -83
  107. package/lib/blobManager/blobManager.js.map +1 -0
  108. package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
  109. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  110. package/lib/blobManager/blobManagerSnapSum.js +75 -0
  111. package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
  112. package/lib/blobManager/index.d.ts +7 -0
  113. package/lib/blobManager/index.d.ts.map +1 -0
  114. package/lib/blobManager/index.js +7 -0
  115. package/lib/blobManager/index.js.map +1 -0
  116. package/lib/channelCollection.d.ts +1 -1
  117. package/lib/channelCollection.d.ts.map +1 -1
  118. package/lib/channelCollection.js +40 -8
  119. package/lib/channelCollection.js.map +1 -1
  120. package/lib/containerRuntime.d.ts +15 -10
  121. package/lib/containerRuntime.d.ts.map +1 -1
  122. package/lib/containerRuntime.js +149 -112
  123. package/lib/containerRuntime.js.map +1 -1
  124. package/lib/dataStoreContext.d.ts +5 -0
  125. package/lib/dataStoreContext.d.ts.map +1 -1
  126. package/lib/dataStoreContext.js +17 -6
  127. package/lib/dataStoreContext.js.map +1 -1
  128. package/lib/gc/garbageCollection.d.ts +1 -1
  129. package/lib/gc/garbageCollection.d.ts.map +1 -1
  130. package/lib/gc/garbageCollection.js +16 -10
  131. package/lib/gc/garbageCollection.js.map +1 -1
  132. package/lib/gc/gcDefinitions.d.ts +4 -2
  133. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  134. package/lib/gc/gcDefinitions.js.map +1 -1
  135. package/lib/gc/gcHelpers.d.ts.map +1 -1
  136. package/lib/gc/gcHelpers.js +12 -0
  137. package/lib/gc/gcHelpers.js.map +1 -1
  138. package/lib/gc/gcTelemetry.d.ts +3 -2
  139. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  140. package/lib/gc/gcTelemetry.js +6 -6
  141. package/lib/gc/gcTelemetry.js.map +1 -1
  142. package/lib/index.d.ts +1 -1
  143. package/lib/index.d.ts.map +1 -1
  144. package/lib/index.js.map +1 -1
  145. package/lib/legacy.d.ts +1 -1
  146. package/lib/metadata.d.ts +7 -1
  147. package/lib/metadata.d.ts.map +1 -1
  148. package/lib/metadata.js +4 -1
  149. package/lib/metadata.js.map +1 -1
  150. package/lib/opLifecycle/batchManager.d.ts +8 -1
  151. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  152. package/lib/opLifecycle/batchManager.js +35 -15
  153. package/lib/opLifecycle/batchManager.js.map +1 -1
  154. package/lib/opLifecycle/definitions.d.ts +1 -1
  155. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  156. package/lib/opLifecycle/definitions.js.map +1 -1
  157. package/lib/opLifecycle/index.d.ts +1 -1
  158. package/lib/opLifecycle/index.d.ts.map +1 -1
  159. package/lib/opLifecycle/index.js +1 -1
  160. package/lib/opLifecycle/index.js.map +1 -1
  161. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  162. package/lib/opLifecycle/opCompressor.js +12 -8
  163. package/lib/opLifecycle/opCompressor.js.map +1 -1
  164. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  165. package/lib/opLifecycle/opGroupingManager.js +14 -11
  166. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  167. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  168. package/lib/opLifecycle/opSplitter.js +11 -6
  169. package/lib/opLifecycle/opSplitter.js.map +1 -1
  170. package/lib/opLifecycle/outbox.d.ts +22 -6
  171. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  172. package/lib/opLifecycle/outbox.js +44 -22
  173. package/lib/opLifecycle/outbox.js.map +1 -1
  174. package/lib/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  175. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  176. package/lib/opLifecycle/remoteMessageProcessor.js +57 -7
  177. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  178. package/lib/packageVersion.d.ts +1 -1
  179. package/lib/packageVersion.js +1 -1
  180. package/lib/packageVersion.js.map +1 -1
  181. package/lib/pendingStateManager.d.ts +39 -13
  182. package/lib/pendingStateManager.d.ts.map +1 -1
  183. package/lib/pendingStateManager.js +99 -34
  184. package/lib/pendingStateManager.js.map +1 -1
  185. package/lib/public.d.ts +1 -1
  186. package/lib/scheduleManager.js +4 -0
  187. package/lib/scheduleManager.js.map +1 -1
  188. package/lib/summary/index.d.ts +1 -1
  189. package/lib/summary/index.d.ts.map +1 -1
  190. package/lib/summary/index.js +1 -1
  191. package/lib/summary/index.js.map +1 -1
  192. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  193. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  194. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  195. package/lib/summary/summaryFormat.d.ts +0 -1
  196. package/lib/summary/summaryFormat.d.ts.map +1 -1
  197. package/lib/summary/summaryFormat.js +5 -2
  198. package/lib/summary/summaryFormat.js.map +1 -1
  199. package/package.json +49 -34
  200. package/src/{blobManager.ts → blobManager/blobManager.ts} +57 -123
  201. package/src/blobManager/blobManagerSnapSum.ts +133 -0
  202. package/src/blobManager/index.ts +19 -0
  203. package/src/channelCollection.ts +48 -11
  204. package/src/containerRuntime.ts +213 -158
  205. package/src/dataStoreContext.ts +30 -6
  206. package/src/gc/garbageCollection.ts +17 -12
  207. package/src/gc/gcDefinitions.ts +7 -2
  208. package/src/gc/gcHelpers.ts +18 -6
  209. package/src/gc/gcTelemetry.ts +20 -8
  210. package/src/index.ts +1 -1
  211. package/src/metadata.ts +11 -1
  212. package/src/opLifecycle/README.md +0 -8
  213. package/src/opLifecycle/batchManager.ts +46 -16
  214. package/src/opLifecycle/definitions.ts +1 -1
  215. package/src/opLifecycle/index.ts +8 -1
  216. package/src/opLifecycle/opCompressor.ts +12 -8
  217. package/src/opLifecycle/opGroupingManager.ts +14 -11
  218. package/src/opLifecycle/opSplitter.ts +10 -6
  219. package/src/opLifecycle/outbox.ts +64 -26
  220. package/src/opLifecycle/remoteMessageProcessor.ts +84 -11
  221. package/src/packageVersion.ts +1 -1
  222. package/src/pendingStateManager.ts +177 -60
  223. package/src/scheduleManager.ts +6 -2
  224. package/src/summary/README.md +81 -0
  225. package/src/summary/index.ts +0 -1
  226. package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
  227. package/src/summary/summaryFormat.ts +4 -2
  228. package/src/summary/summaryFormats.md +69 -8
  229. package/tsconfig.json +0 -1
  230. package/dist/blobManager.d.ts.map +0 -1
  231. package/dist/blobManager.js.map +0 -1
  232. package/lib/blobManager.d.ts.map +0 -1
  233. package/lib/blobManager.js.map +0 -1
  234. package/src/summary/images/appTree.png +0 -0
  235. package/src/summary/images/protocolAndAppTree.png +0 -0
  236. package/src/summary/images/summaryTree.png +0 -0
@@ -59,27 +59,28 @@ export class OpGroupingManager {
59
59
  public groupBatch(batch: IBatch): IBatch<[BatchMessage]> {
60
60
  assert(this.shouldGroup(batch), 0x946 /* cannot group the provided batch */);
61
61
 
62
- if (batch.content.length >= 1000) {
62
+ if (batch.messages.length >= 1000) {
63
63
  this.logger.sendTelemetryEvent({
64
64
  eventName: "GroupLargeBatch",
65
- length: batch.content.length,
65
+ length: batch.messages.length,
66
66
  threshold: this.config.opCountThreshold,
67
67
  reentrant: batch.hasReentrantOps,
68
- referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
68
+ // Non null asserting here because of the length check above
69
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
70
+ referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
69
71
  });
70
72
  }
71
73
 
72
- for (const message of batch.content) {
74
+ for (const message of batch.messages) {
73
75
  if (message.metadata) {
74
- const keys = Object.keys(message.metadata);
75
- assert(keys.length < 2, 0x5dd /* cannot group ops with metadata */);
76
- assert(keys.length === 0 || keys[0] === "batch", 0x5de /* unexpected op metadata */);
76
+ const { batch: _batch, batchId, ...rest } = message.metadata;
77
+ assert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);
77
78
  }
78
79
  }
79
80
 
80
81
  const serializedContent = JSON.stringify({
81
82
  type: OpGroupingManager.groupedBatchOp,
82
- contents: batch.content.map<IGroupedMessage>((message) => ({
83
+ contents: batch.messages.map<IGroupedMessage>((message) => ({
83
84
  contents: message.contents === undefined ? undefined : JSON.parse(message.contents),
84
85
  metadata: message.metadata,
85
86
  compression: message.compression,
@@ -88,10 +89,12 @@ export class OpGroupingManager {
88
89
 
89
90
  const groupedBatch: IBatch<[BatchMessage]> = {
90
91
  ...batch,
91
- content: [
92
+ messages: [
92
93
  {
93
94
  metadata: undefined,
94
- referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
95
+ // TODO why are we non null asserting here?
96
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97
+ referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
95
98
  contents: serializedContent,
96
99
  },
97
100
  ],
@@ -118,7 +121,7 @@ export class OpGroupingManager {
118
121
  // Grouped batching must be enabled
119
122
  this.config.groupedBatchingEnabled &&
120
123
  // The number of ops in the batch must surpass the configured threshold
121
- batch.content.length >= this.config.opCountThreshold &&
124
+ batch.messages.length >= this.config.opCountThreshold &&
122
125
  // Support for reentrant batches must be explicitly enabled
123
126
  (this.config.reentrantBatchGroupingEnabled || batch.hasReentrantOps !== true)
124
127
  );
@@ -121,7 +121,7 @@ export class OpSplitter {
121
121
  public splitFirstBatchMessage(batch: IBatch): IBatch {
122
122
  assert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
123
123
  assert(
124
- batch.contentSizeInBytes > 0 && batch.content.length > 0,
124
+ batch.contentSizeInBytes > 0 && batch.messages.length > 0,
125
125
  0x514 /* Batch needs to be non-empty */,
126
126
  );
127
127
  assert(
@@ -134,13 +134,15 @@ export class OpSplitter {
134
134
  0x516 /* Chunk size needs to be smaller than the max batch size */,
135
135
  );
136
136
 
137
- const firstMessage = batch.content[0]; // we expect this to be the large compressed op, which needs to be split
137
+ // Non null asserting here because of the length check above
138
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
139
+ const firstMessage = batch.messages[0]!; // we expect this to be the large compressed op, which needs to be split
138
140
  assert(
139
141
  (firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,
140
142
  0x518 /* First message in the batch needs to be chunkable */,
141
143
  );
142
144
 
143
- const restOfMessages = batch.content.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
145
+ const restOfMessages = batch.messages.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
144
146
  const socketSize = estimateSocketSize(batch);
145
147
  const chunks = splitOp(
146
148
  firstMessage,
@@ -163,7 +165,9 @@ export class OpSplitter {
163
165
  // The last chunk will be part of the new batch and needs to
164
166
  // preserve the batch metadata of the original batch
165
167
  const lastChunk = chunkToBatchMessage(
166
- chunks[chunks.length - 1],
168
+ // Non null asserting here because of the length assert above
169
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
+ chunks[chunks.length - 1]!,
167
171
  batch.referenceSequenceNumber,
168
172
  { batch: firstMessage.metadata?.batch },
169
173
  );
@@ -171,7 +175,7 @@ export class OpSplitter {
171
175
  this.logger.sendPerformanceEvent({
172
176
  // Used to be "Chunked compressed batch"
173
177
  eventName: "CompressedChunkedBatch",
174
- length: batch.content.length,
178
+ length: batch.messages.length,
175
179
  sizeInBytes: batch.contentSizeInBytes,
176
180
  chunks: chunks.length,
177
181
  chunkSizeInBytes: this.chunkSizeInBytes,
@@ -179,7 +183,7 @@ export class OpSplitter {
179
183
  });
180
184
 
181
185
  return {
182
- content: [lastChunk, ...restOfMessages],
186
+ messages: [lastChunk, ...restOfMessages],
183
187
  contentSizeInBytes: lastChunk.contents?.length ?? 0,
184
188
  referenceSequenceNumber: batch.referenceSequenceNumber,
185
189
  };
@@ -9,19 +9,20 @@ import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
9
9
  import { assert } from "@fluidframework/core-utils/internal";
10
10
  import {
11
11
  GenericError,
12
- MonitoringContext,
13
12
  UsageError,
14
- createChildMonitoringContext,
13
+ createChildLogger,
14
+ type ITelemetryLoggerExt,
15
15
  } from "@fluidframework/telemetry-utils/internal";
16
16
 
17
17
  import { ICompressionRuntimeOptions } from "../containerRuntime.js";
18
- import { IPendingBatchMessage, PendingStateManager } from "../pendingStateManager.js";
18
+ import { PendingMessageResubmitData, PendingStateManager } from "../pendingStateManager.js";
19
19
 
20
20
  import {
21
21
  BatchManager,
22
22
  BatchSequenceNumbers,
23
23
  estimateSocketSize,
24
24
  sequenceNumbersMatch,
25
+ type BatchId,
25
26
  } from "./batchManager.js";
26
27
  import { BatchMessage, IBatch, IBatchCheckpoint } from "./definitions.js";
27
28
  import { OpCompressor } from "./opCompressor.js";
@@ -48,7 +49,7 @@ export interface IOutboxParameters {
48
49
  readonly logger: ITelemetryBaseLogger;
49
50
  readonly groupingManager: OpGroupingManager;
50
51
  readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
51
- readonly reSubmit: (message: IPendingBatchMessage) => void;
52
+ readonly reSubmit: (message: PendingMessageResubmitData) => void;
52
53
  readonly opReentrancy: () => boolean;
53
54
  readonly closeContainer: (error?: ICriticalContainerError) => void;
54
55
  }
@@ -87,8 +88,15 @@ export function getLongStack<T>(action: () => T, length: number = 50): T {
87
88
  }
88
89
  }
89
90
 
91
+ /**
92
+ * The Outbox collects messages submitted by the ContainerRuntime into a batch,
93
+ * and then flushes the batch when requested.
94
+ *
95
+ * @remarks There are actually multiple independent batches (some are for a specific message type),
96
+ * to support slight variation in semantics for each batch (e.g. support for rebasing or grouping).
97
+ */
90
98
  export class Outbox {
91
- private readonly mc: MonitoringContext;
99
+ private readonly logger: ITelemetryLoggerExt;
92
100
  private readonly mainBatch: BatchManager;
93
101
  private readonly blobAttachBatch: BatchManager;
94
102
  private readonly idAllocationBatch: BatchManager;
@@ -105,7 +113,8 @@ export class Outbox {
105
113
  private mismatchedOpsReported = 0;
106
114
 
107
115
  constructor(private readonly params: IOutboxParameters) {
108
- this.mc = createChildMonitoringContext({ logger: params.logger, namespace: "Outbox" });
116
+ this.logger = createChildLogger({ logger: params.logger, namespace: "Outbox" });
117
+
109
118
  const isCompressionEnabled =
110
119
  this.params.config.compressionOptions.minimumBatchSizeInBytes !==
111
120
  Number.POSITIVE_INFINITY;
@@ -158,7 +167,7 @@ export class Outbox {
158
167
  }
159
168
 
160
169
  if (++this.mismatchedOpsReported <= this.maxMismatchedOpsToReport) {
161
- this.mc.logger.sendTelemetryEvent(
170
+ this.logger.sendTelemetryEvent(
162
171
  {
163
172
  category: this.params.config.disablePartialFlush ? "error" : "generic",
164
173
  eventName: "ReferenceSequenceNumberMismatch",
@@ -225,28 +234,48 @@ export class Outbox {
225
234
  }
226
235
  }
227
236
 
228
- public flush() {
237
+ /**
238
+ * Flush all the batches to the ordering service.
239
+ * This method is expected to be called at the end of a batch.
240
+ * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
241
+ * with the given Batch ID, which must be preserved
242
+ */
243
+ public flush(resubmittingBatchId?: BatchId) {
229
244
  if (this.isContextReentrant()) {
230
245
  const error = new UsageError("Flushing is not supported inside DDS event handlers");
231
246
  this.params.closeContainer(error);
232
247
  throw error;
233
248
  }
234
249
 
235
- this.flushAll();
250
+ this.flushAll(resubmittingBatchId);
236
251
  }
237
252
 
238
- private flushAll() {
253
+ private flushAll(resubmittingBatchId?: BatchId) {
254
+ // Don't use resubmittingBatchId for idAllocationBatch.
255
+ // ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
239
256
  this.flushInternal(this.idAllocationBatch);
240
- this.flushInternal(this.blobAttachBatch, true /* disableGroupedBatching */);
241
- this.flushInternal(this.mainBatch);
257
+ this.flushInternal(
258
+ this.blobAttachBatch,
259
+ true /* disableGroupedBatching */,
260
+ resubmittingBatchId,
261
+ );
262
+ this.flushInternal(
263
+ this.mainBatch,
264
+ false /* disableGroupedBatching */,
265
+ resubmittingBatchId,
266
+ );
242
267
  }
243
268
 
244
- private flushInternal(batchManager: BatchManager, disableGroupedBatching: boolean = false) {
269
+ private flushInternal(
270
+ batchManager: BatchManager,
271
+ disableGroupedBatching: boolean = false,
272
+ resubmittingBatchId?: BatchId,
273
+ ) {
245
274
  if (batchManager.empty) {
246
275
  return;
247
276
  }
248
277
 
249
- const rawBatch = batchManager.popBatch();
278
+ const rawBatch = batchManager.popBatch(resubmittingBatchId);
250
279
  const shouldGroup =
251
280
  !disableGroupedBatching && this.params.groupingManager.shouldGroup(rawBatch);
252
281
  if (batchManager.options.canRebase && rawBatch.hasReentrantOps === true && shouldGroup) {
@@ -267,9 +296,13 @@ export class Outbox {
267
296
  shouldGroup ? this.params.groupingManager.groupBatch(rawBatch) : rawBatch,
268
297
  );
269
298
  clientSequenceNumber = this.sendBatch(processedBatch);
299
+ assert(
300
+ clientSequenceNumber === undefined || clientSequenceNumber >= 0,
301
+ "unexpected negative clientSequenceNumber (empty batch should yield undefined)",
302
+ );
270
303
  }
271
304
 
272
- this.params.pendingStateManager.onFlushBatch(rawBatch.content, clientSequenceNumber);
305
+ this.params.pendingStateManager.onFlushBatch(rawBatch.messages, clientSequenceNumber);
273
306
  }
274
307
 
275
308
  /**
@@ -283,7 +316,7 @@ export class Outbox {
283
316
  assert(batchManager.options.canRebase, 0x9a7 /* BatchManager does not support rebase */);
284
317
 
285
318
  this.rebasing = true;
286
- for (const message of rawBatch.content) {
319
+ for (const message of rawBatch.messages) {
287
320
  this.params.reSubmit({
288
321
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
289
322
  content: message.contents!,
@@ -293,10 +326,10 @@ export class Outbox {
293
326
  }
294
327
 
295
328
  if (this.batchRebasesToReport > 0) {
296
- this.mc.logger.sendTelemetryEvent(
329
+ this.logger.sendTelemetryEvent(
297
330
  {
298
331
  eventName: "BatchRebase",
299
- length: rawBatch.content.length,
332
+ length: rawBatch.messages.length,
300
333
  referenceSequenceNumber: rawBatch.referenceSequenceNumber,
301
334
  },
302
335
  new UsageError("BatchRebase"),
@@ -323,7 +356,7 @@ export class Outbox {
323
356
  */
324
357
  private compressBatch(batch: IBatch): IBatch {
325
358
  if (
326
- batch.content.length === 0 ||
359
+ batch.messages.length === 0 ||
327
360
  this.params.config.compressionOptions === undefined ||
328
361
  this.params.config.compressionOptions.minimumBatchSizeInBytes >
329
362
  batch.contentSizeInBytes ||
@@ -345,7 +378,7 @@ export class Outbox {
345
378
  throw new GenericError("BatchTooLarge", /* error */ undefined, {
346
379
  batchSize: batch.contentSizeInBytes,
347
380
  compressedBatchSize: compressedBatch.contentSizeInBytes,
348
- count: compressedBatch.content.length,
381
+ count: compressedBatch.messages.length,
349
382
  limit: this.params.config.maxBatchSizeInBytes,
350
383
  chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
351
384
  compressionOptions: JSON.stringify(this.params.config.compressionOptions),
@@ -363,16 +396,16 @@ export class Outbox {
363
396
  * @returns the clientSequenceNumber of the start of the batch, or undefined if nothing was sent
364
397
  */
365
398
  private sendBatch(batch: IBatch) {
366
- const length = batch.content.length;
399
+ const length = batch.messages.length;
367
400
  if (length === 0) {
368
401
  return undefined; // Nothing submitted
369
402
  }
370
403
 
371
404
  const socketSize = estimateSocketSize(batch);
372
405
  if (socketSize >= this.params.config.maxBatchSizeInBytes) {
373
- this.mc.logger.sendPerformanceEvent({
406
+ this.logger.sendPerformanceEvent({
374
407
  eventName: "LargeBatch",
375
- length: batch.content.length,
408
+ length: batch.messages.length,
376
409
  sizeInBytes: batch.contentSizeInBytes,
377
410
  socketSize,
378
411
  });
@@ -383,7 +416,9 @@ export class Outbox {
383
416
  // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
384
417
  // version that has support for batches (submitBatchFn)
385
418
  assert(
386
- batch.content[0].compression === undefined,
419
+ // Non null asserting here because of the length check above
420
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
421
+ batch.messages[0]!.compression === undefined,
387
422
  0x5a6 /* Compression should not have happened if the loader does not support it */,
388
423
  );
389
424
 
@@ -391,7 +426,7 @@ export class Outbox {
391
426
  } else {
392
427
  assert(batch.referenceSequenceNumber !== undefined, 0x58e /* Batch must not be empty */);
393
428
  clientSequenceNumber = this.params.submitBatchFn(
394
- batch.content.map((message) => ({
429
+ batch.messages.map<IBatchMessage>((message) => ({
395
430
  contents: message.contents,
396
431
  metadata: message.metadata,
397
432
  compression: message.compression,
@@ -407,7 +442,10 @@ export class Outbox {
407
442
  return clientSequenceNumber;
408
443
  }
409
444
 
410
- public checkpoint() {
445
+ /**
446
+ * @returns A checkpoint object per batch that facilitates iterating over the batch messages when rolling back.
447
+ */
448
+ public getBatchCheckpoints() {
411
449
  // This variable is declared with a specific type so that we have a standard import of the IBatchCheckpoint type.
412
450
  // When the type is inferred, the generated .d.ts uses a dynamic import which doesn't resolve.
413
451
  const mainBatch: IBatchCheckpoint = this.mainBatch.checkpoint();
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { assert } from "@fluidframework/core-utils/internal";
6
7
  import {
7
8
  MessageType,
8
9
  ISequencedDocumentMessage,
@@ -12,9 +13,9 @@ import {
12
13
  ContainerMessageType,
13
14
  type InboundContainerRuntimeMessage,
14
15
  type InboundSequencedContainerRuntimeMessage,
15
- type InboundSequencedContainerRuntimeMessageOrSystemMessage,
16
16
  type InboundSequencedRecentlyAddedContainerRuntimeMessage,
17
17
  } from "../messageTypes.js";
18
+ import { asBatchMetadata } from "../metadata.js";
18
19
 
19
20
  import { OpDecompressor } from "./opDecompressor.js";
20
21
  import { OpGroupingManager, isGroupedBatch } from "./opGroupingManager.js";
@@ -27,6 +28,15 @@ import { OpSplitter, isChunkedMessage } from "./opSplitter.js";
27
28
  * @internal
28
29
  */
29
30
  export class RemoteMessageProcessor {
31
+ /**
32
+ * Client Sequence Number of the first message in the current batch being processed.
33
+ * If undefined, we are expecting the next message to start a new batch.
34
+ *
35
+ * @remarks For chunked batches, this is the CSN of the "representative" chunk (the final chunk)
36
+ */
37
+ private batchStartCsn: number | undefined;
38
+ private readonly processorBatch: InboundSequencedContainerRuntimeMessage[] = [];
39
+
30
40
  constructor(
31
41
  private readonly opSplitter: OpSplitter,
32
42
  private readonly opDecompressor: OpDecompressor,
@@ -42,7 +52,7 @@ export class RemoteMessageProcessor {
42
52
  }
43
53
 
44
54
  /**
45
- * Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire
55
+ * Ungroups and Unchunks the runtime ops of a batch received over the wire
46
56
  * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized
47
57
  * (grouped, compressed, and/or chunked).
48
58
  * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure
@@ -57,21 +67,24 @@ export class RemoteMessageProcessor {
57
67
  * 3. If grouped, ungroup the message
58
68
  * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound
59
69
  *
60
- * @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.
61
- * For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),
62
- * a singleton array [remoteMessageCopy] is returned
70
+ * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
71
+ * or undefined if the batch is not yet complete.
63
72
  */
64
- public process(
65
- remoteMessageCopy: ISequencedDocumentMessage,
66
- ): InboundSequencedContainerRuntimeMessageOrSystemMessage[] {
73
+ public process(remoteMessageCopy: ISequencedDocumentMessage):
74
+ | {
75
+ messages: InboundSequencedContainerRuntimeMessage[];
76
+ batchStartCsn: number;
77
+ }
78
+ | undefined {
67
79
  let message = remoteMessageCopy;
80
+
68
81
  ensureContentsDeserialized(message);
69
82
 
70
83
  if (isChunkedMessage(message)) {
71
84
  const chunkProcessingResult = this.opSplitter.processChunk(message);
72
85
  // Only continue further if current chunk is the final chunk
73
86
  if (!chunkProcessingResult.isFinalChunk) {
74
- return [];
87
+ return;
75
88
  }
76
89
  // This message will always be compressed
77
90
  message = chunkProcessingResult.message;
@@ -90,12 +103,72 @@ export class RemoteMessageProcessor {
90
103
  }
91
104
 
92
105
  if (isGroupedBatch(message)) {
93
- return this.opGroupingManager.ungroupOp(message).map(unpack);
106
+ // We should be awaiting a new batch (batchStartCsn undefined)
107
+ assert(this.batchStartCsn === undefined, "Grouped batch interrupting another batch");
108
+ assert(
109
+ this.processorBatch.length === 0,
110
+ "Processor batch should be empty on grouped batch",
111
+ );
112
+ return {
113
+ messages: this.opGroupingManager.ungroupOp(message).map(unpack),
114
+ batchStartCsn: message.clientSequenceNumber,
115
+ };
94
116
  }
95
117
 
118
+ const batchStartCsn = this.getAndUpdateBatchStartCsn(message);
119
+
96
120
  // Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
97
121
  unpackRuntimeMessage(message);
98
- return [message as InboundSequencedContainerRuntimeMessageOrSystemMessage];
122
+ this.processorBatch.push(message as InboundSequencedContainerRuntimeMessage);
123
+
124
+ // this.batchStartCsn is undefined only if we have processed all messages in the batch.
125
+ // If it's still defined, we're still in the middle of a batch, so we return nothing, letting
126
+ // containerRuntime know that we're waiting for more messages to complete the batch.
127
+ if (this.batchStartCsn !== undefined) {
128
+ // batch not yet complete
129
+ return undefined;
130
+ }
131
+
132
+ const messages = [...this.processorBatch];
133
+ this.processorBatch.length = 0;
134
+ return {
135
+ messages,
136
+ batchStartCsn,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Based on pre-existing batch tracking info and the current message's batch metadata,
142
+ * this will return the starting CSN for this message's batch, and will also update
143
+ * the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.
144
+ */
145
+ private getAndUpdateBatchStartCsn(message: ISequencedDocumentMessage): number {
146
+ const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
147
+ if (this.batchStartCsn === undefined) {
148
+ // We are waiting for a new batch
149
+ assert(batchMetadataFlag !== false, "Unexpected batch end marker");
150
+
151
+ // Start of a new multi-message batch
152
+ if (batchMetadataFlag === true) {
153
+ this.batchStartCsn = message.clientSequenceNumber;
154
+ return this.batchStartCsn;
155
+ }
156
+
157
+ // Single-message batch (Since metadata flag is undefined)
158
+ // IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.
159
+ return message.clientSequenceNumber;
160
+ }
161
+
162
+ // We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn
163
+ const batchStartCsn = this.batchStartCsn;
164
+
165
+ assert(batchMetadataFlag !== true, "Unexpected batch start marker");
166
+ if (batchMetadataFlag === false) {
167
+ // Batch end? Then get ready for the next batch to start
168
+ this.batchStartCsn = undefined;
169
+ }
170
+
171
+ return batchStartCsn;
99
172
  }
100
173
  }
101
174
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.1.0-276326";
9
+ export const pkgVersion = "2.1.0-281041";