@fluidframework/container-runtime 2.0.0-internal.5.1.1 → 2.0.0-internal.5.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 (248) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/blobManager.d.ts +5 -2
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +51 -22
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/connectionTelemetry.d.ts.map +1 -1
  7. package/dist/connectionTelemetry.js +8 -1
  8. package/dist/connectionTelemetry.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +5 -0
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +27 -7
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +1 -2
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +4 -3
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.d.ts +2 -1
  18. package/dist/dataStoreContexts.d.ts.map +1 -1
  19. package/dist/dataStoreContexts.js +2 -1
  20. package/dist/dataStoreContexts.js.map +1 -1
  21. package/dist/dataStores.d.ts +1 -1
  22. package/dist/dataStores.d.ts.map +1 -1
  23. package/dist/dataStores.js +2 -1
  24. package/dist/dataStores.js.map +1 -1
  25. package/dist/gc/garbageCollection.d.ts.map +1 -1
  26. package/dist/gc/garbageCollection.js +4 -3
  27. package/dist/gc/garbageCollection.js.map +1 -1
  28. package/dist/gc/gcDefinitions.d.ts +0 -1
  29. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  30. package/dist/gc/gcDefinitions.js.map +1 -1
  31. package/dist/gc/gcTelemetry.d.ts +1 -1
  32. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  33. package/dist/gc/gcTelemetry.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/metadata.d.ts +18 -0
  38. package/dist/metadata.d.ts.map +1 -0
  39. package/dist/metadata.js +7 -0
  40. package/dist/metadata.js.map +1 -0
  41. package/dist/opLifecycle/batchManager.d.ts +2 -1
  42. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  43. package/dist/opLifecycle/batchManager.js +5 -1
  44. package/dist/opLifecycle/batchManager.js.map +1 -1
  45. package/dist/opLifecycle/definitions.d.ts +11 -0
  46. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  47. package/dist/opLifecycle/definitions.js.map +1 -1
  48. package/dist/opLifecycle/index.d.ts +1 -1
  49. package/dist/opLifecycle/index.d.ts.map +1 -1
  50. package/dist/opLifecycle/index.js +2 -1
  51. package/dist/opLifecycle/index.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  53. package/dist/opLifecycle/opDecompressor.js +14 -8
  54. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  55. package/dist/opLifecycle/opGroupingManager.d.ts +1 -1
  56. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  57. package/dist/opLifecycle/opGroupingManager.js +2 -6
  58. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  60. package/dist/opLifecycle/opSplitter.js +2 -0
  61. package/dist/opLifecycle/opSplitter.js.map +1 -1
  62. package/dist/opLifecycle/outbox.d.ts +33 -2
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  64. package/dist/opLifecycle/outbox.js +128 -42
  65. package/dist/opLifecycle/outbox.js.map +1 -1
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  67. package/dist/opLifecycle/remoteMessageProcessor.js +3 -1
  68. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  69. package/dist/packageVersion.d.ts +1 -1
  70. package/dist/packageVersion.js +1 -1
  71. package/dist/packageVersion.js.map +1 -1
  72. package/dist/pendingStateManager.d.ts +7 -2
  73. package/dist/pendingStateManager.d.ts.map +1 -1
  74. package/dist/pendingStateManager.js +36 -21
  75. package/dist/pendingStateManager.js.map +1 -1
  76. package/dist/scheduleManager.d.ts.map +1 -1
  77. package/dist/scheduleManager.js +8 -2
  78. package/dist/scheduleManager.js.map +1 -1
  79. package/dist/summary/index.d.ts +1 -1
  80. package/dist/summary/index.d.ts.map +1 -1
  81. package/dist/summary/index.js.map +1 -1
  82. package/dist/summary/runningSummarizer.d.ts +1 -1
  83. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  84. package/dist/summary/runningSummarizer.js.map +1 -1
  85. package/dist/summary/summarizerNode/summarizerNode.d.ts +1 -1
  86. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  87. package/dist/summary/summarizerNode/summarizerNode.js +3 -3
  88. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  89. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  90. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +3 -2
  91. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  92. package/dist/summary/summarizerTypes.d.ts +2 -1
  93. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  94. package/dist/summary/summarizerTypes.js.map +1 -1
  95. package/dist/summary/summaryCollection.d.ts +2 -1
  96. package/dist/summary/summaryCollection.d.ts.map +1 -1
  97. package/dist/summary/summaryCollection.js +4 -0
  98. package/dist/summary/summaryCollection.js.map +1 -1
  99. package/dist/summary/summaryFormat.d.ts +1 -0
  100. package/dist/summary/summaryFormat.d.ts.map +1 -1
  101. package/dist/summary/summaryFormat.js +2 -1
  102. package/dist/summary/summaryFormat.js.map +1 -1
  103. package/dist/summary/summaryGenerator.d.ts +1 -1
  104. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  105. package/dist/summary/summaryGenerator.js.map +1 -1
  106. package/dist/summary/summaryManager.d.ts +2 -1
  107. package/dist/summary/summaryManager.d.ts.map +1 -1
  108. package/dist/summary/summaryManager.js.map +1 -1
  109. package/lib/blobManager.d.ts +5 -2
  110. package/lib/blobManager.d.ts.map +1 -1
  111. package/lib/blobManager.js +51 -22
  112. package/lib/blobManager.js.map +1 -1
  113. package/lib/connectionTelemetry.d.ts.map +1 -1
  114. package/lib/connectionTelemetry.js +8 -1
  115. package/lib/connectionTelemetry.js.map +1 -1
  116. package/lib/containerRuntime.d.ts +5 -0
  117. package/lib/containerRuntime.d.ts.map +1 -1
  118. package/lib/containerRuntime.js +29 -9
  119. package/lib/containerRuntime.js.map +1 -1
  120. package/lib/dataStoreContext.d.ts +1 -2
  121. package/lib/dataStoreContext.d.ts.map +1 -1
  122. package/lib/dataStoreContext.js +4 -3
  123. package/lib/dataStoreContext.js.map +1 -1
  124. package/lib/dataStoreContexts.d.ts +2 -1
  125. package/lib/dataStoreContexts.d.ts.map +1 -1
  126. package/lib/dataStoreContexts.js +2 -1
  127. package/lib/dataStoreContexts.js.map +1 -1
  128. package/lib/dataStores.d.ts +1 -1
  129. package/lib/dataStores.d.ts.map +1 -1
  130. package/lib/dataStores.js +2 -1
  131. package/lib/dataStores.js.map +1 -1
  132. package/lib/gc/garbageCollection.d.ts.map +1 -1
  133. package/lib/gc/garbageCollection.js +2 -1
  134. package/lib/gc/garbageCollection.js.map +1 -1
  135. package/lib/gc/gcDefinitions.d.ts +0 -1
  136. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  137. package/lib/gc/gcDefinitions.js.map +1 -1
  138. package/lib/gc/gcTelemetry.d.ts +1 -1
  139. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  140. package/lib/gc/gcTelemetry.js.map +1 -1
  141. package/lib/index.d.ts +1 -1
  142. package/lib/index.d.ts.map +1 -1
  143. package/lib/index.js.map +1 -1
  144. package/lib/metadata.d.ts +18 -0
  145. package/lib/metadata.d.ts.map +1 -0
  146. package/lib/metadata.js +6 -0
  147. package/lib/metadata.js.map +1 -0
  148. package/lib/opLifecycle/batchManager.d.ts +2 -1
  149. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  150. package/lib/opLifecycle/batchManager.js +5 -1
  151. package/lib/opLifecycle/batchManager.js.map +1 -1
  152. package/lib/opLifecycle/definitions.d.ts +11 -0
  153. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  154. package/lib/opLifecycle/definitions.js.map +1 -1
  155. package/lib/opLifecycle/index.d.ts +1 -1
  156. package/lib/opLifecycle/index.d.ts.map +1 -1
  157. package/lib/opLifecycle/index.js +1 -1
  158. package/lib/opLifecycle/index.js.map +1 -1
  159. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  160. package/lib/opLifecycle/opDecompressor.js +14 -8
  161. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  162. package/lib/opLifecycle/opGroupingManager.d.ts +1 -1
  163. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  164. package/lib/opLifecycle/opGroupingManager.js +2 -6
  165. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  166. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  167. package/lib/opLifecycle/opSplitter.js +2 -0
  168. package/lib/opLifecycle/opSplitter.js.map +1 -1
  169. package/lib/opLifecycle/outbox.d.ts +33 -2
  170. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  171. package/lib/opLifecycle/outbox.js +126 -41
  172. package/lib/opLifecycle/outbox.js.map +1 -1
  173. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  174. package/lib/opLifecycle/remoteMessageProcessor.js +3 -1
  175. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  176. package/lib/packageVersion.d.ts +1 -1
  177. package/lib/packageVersion.js +1 -1
  178. package/lib/packageVersion.js.map +1 -1
  179. package/lib/pendingStateManager.d.ts +7 -2
  180. package/lib/pendingStateManager.d.ts.map +1 -1
  181. package/lib/pendingStateManager.js +36 -21
  182. package/lib/pendingStateManager.js.map +1 -1
  183. package/lib/scheduleManager.d.ts.map +1 -1
  184. package/lib/scheduleManager.js +8 -2
  185. package/lib/scheduleManager.js.map +1 -1
  186. package/lib/summary/index.d.ts +1 -1
  187. package/lib/summary/index.d.ts.map +1 -1
  188. package/lib/summary/index.js.map +1 -1
  189. package/lib/summary/runningSummarizer.d.ts +1 -1
  190. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  191. package/lib/summary/runningSummarizer.js.map +1 -1
  192. package/lib/summary/summarizerNode/summarizerNode.d.ts +1 -1
  193. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  194. package/lib/summary/summarizerNode/summarizerNode.js +3 -3
  195. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  196. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  197. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +2 -1
  198. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  199. package/lib/summary/summarizerTypes.d.ts +2 -1
  200. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  201. package/lib/summary/summarizerTypes.js.map +1 -1
  202. package/lib/summary/summaryCollection.d.ts +2 -1
  203. package/lib/summary/summaryCollection.d.ts.map +1 -1
  204. package/lib/summary/summaryCollection.js +4 -0
  205. package/lib/summary/summaryCollection.js.map +1 -1
  206. package/lib/summary/summaryFormat.d.ts +1 -0
  207. package/lib/summary/summaryFormat.d.ts.map +1 -1
  208. package/lib/summary/summaryFormat.js +2 -1
  209. package/lib/summary/summaryFormat.js.map +1 -1
  210. package/lib/summary/summaryGenerator.d.ts +1 -1
  211. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  212. package/lib/summary/summaryGenerator.js.map +1 -1
  213. package/lib/summary/summaryManager.d.ts +2 -1
  214. package/lib/summary/summaryManager.d.ts.map +1 -1
  215. package/lib/summary/summaryManager.js.map +1 -1
  216. package/package.json +20 -19
  217. package/src/blobManager.ts +68 -27
  218. package/src/connectionTelemetry.ts +10 -1
  219. package/src/containerRuntime.ts +37 -14
  220. package/src/dataStoreContext.ts +12 -5
  221. package/src/dataStoreContexts.ts +4 -2
  222. package/src/dataStores.ts +8 -3
  223. package/src/gc/garbageCollection.ts +2 -1
  224. package/src/gc/gcDefinitions.ts +0 -1
  225. package/src/gc/gcTelemetry.ts +1 -1
  226. package/src/index.ts +1 -0
  227. package/src/metadata.ts +19 -0
  228. package/src/opLifecycle/README.md +20 -0
  229. package/src/opLifecycle/batchManager.ts +9 -1
  230. package/src/opLifecycle/definitions.ts +11 -0
  231. package/src/opLifecycle/index.ts +1 -1
  232. package/src/opLifecycle/opDecompressor.ts +41 -13
  233. package/src/opLifecycle/opGroupingManager.ts +14 -7
  234. package/src/opLifecycle/opSplitter.ts +3 -1
  235. package/src/opLifecycle/outbox.ts +163 -49
  236. package/src/opLifecycle/remoteMessageProcessor.ts +5 -1
  237. package/src/packageVersion.ts +1 -1
  238. package/src/pendingStateManager.ts +56 -41
  239. package/src/scheduleManager.ts +15 -6
  240. package/src/summary/index.ts +1 -0
  241. package/src/summary/runningSummarizer.ts +1 -1
  242. package/src/summary/summarizerNode/summarizerNode.ts +4 -4
  243. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +2 -1
  244. package/src/summary/summarizerTypes.ts +2 -1
  245. package/src/summary/summaryCollection.ts +8 -3
  246. package/src/summary/summaryFormat.ts +5 -1
  247. package/src/summary/summaryGenerator.ts +1 -1
  248. package/src/summary/summaryManager.ts +2 -1
@@ -10,11 +10,11 @@ import {
10
10
  MonitoringContext,
11
11
  } from "@fluidframework/telemetry-utils";
12
12
  import { assert } from "@fluidframework/common-utils";
13
- import { IContainerContext } from "@fluidframework/container-definitions";
13
+ import { IContainerContext, ICriticalContainerError } from "@fluidframework/container-definitions";
14
14
  import { GenericError, UsageError } from "@fluidframework/container-utils";
15
15
  import { MessageType } from "@fluidframework/protocol-definitions";
16
16
  import { ICompressionRuntimeOptions } from "../containerRuntime";
17
- import { PendingStateManager } from "../pendingStateManager";
17
+ import { IPendingBatchMessage, PendingStateManager } from "../pendingStateManager";
18
18
  import {
19
19
  BatchManager,
20
20
  BatchSequenceNumbers,
@@ -31,6 +31,7 @@ export interface IOutboxConfig {
31
31
  // The maximum size of a batch that we can send over the wire.
32
32
  readonly maxBatchSizeInBytes: number;
33
33
  readonly disablePartialFlush: boolean;
34
+ readonly enableGroupedBatching: boolean;
34
35
  }
35
36
 
36
37
  export interface IOutboxParameters {
@@ -43,18 +44,40 @@ export interface IOutboxParameters {
43
44
  readonly logger: ITelemetryLoggerExt;
44
45
  readonly groupingManager: OpGroupingManager;
45
46
  readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
47
+ readonly reSubmit: (message: IPendingBatchMessage) => void;
48
+ readonly opReentrancy: () => boolean;
49
+ readonly closeContainer: (error?: ICriticalContainerError) => void;
46
50
  }
47
51
 
48
- function getLongStack(action: () => Error): Error {
49
- // Increase the stack trace limit temporarily, so as to debug better in case it occurs.
52
+ /**
53
+ * Temporarily increase the stack limit while executing the provided action.
54
+ * If a negative value is provided for `length`, no stack frames will be collected.
55
+ * If Infinity is provided, all frames will be collected.
56
+ *
57
+ * ADO:4663 - add this to the common packages.
58
+ *
59
+ * @param action - action which returns an error
60
+ * @param length - number of stack frames to collect, 50 if unspecified.
61
+ * @returns the result of the action provided
62
+ */
63
+ export function getLongStack<T>(action: () => T, length: number = 50): T {
64
+ const errorObj = Error as any;
65
+ if (
66
+ (
67
+ Object.getOwnPropertyDescriptor(errorObj, "stackTraceLimit") ||
68
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(errorObj), "stackTraceLimit") ||
69
+ {}
70
+ ).writable !== true
71
+ ) {
72
+ return action();
73
+ }
74
+
75
+ const originalStackTraceLimit = errorObj.stackTraceLimit;
50
76
  try {
51
- const originalStackTraceLimit = (Error as any).stackTraceLimit;
52
- (Error as any).stackTraceLimit = 50;
53
- const result = action();
54
- (Error as any).stackTraceLimit = originalStackTraceLimit;
55
- return result;
56
- } catch (error) {
77
+ errorObj.stackTraceLimit = length;
57
78
  return action();
79
+ } finally {
80
+ errorObj.stackTraceLimit = originalStackTraceLimit;
58
81
  }
59
82
  }
60
83
 
@@ -62,7 +85,10 @@ export class Outbox {
62
85
  private readonly mc: MonitoringContext;
63
86
  private readonly attachFlowBatch: BatchManager;
64
87
  private readonly mainBatch: BatchManager;
88
+ private readonly blobAttachBatch: BatchManager;
65
89
  private readonly defaultAttachFlowSoftLimitInBytes = 320 * 1024;
90
+ private batchRebasesToReport = 5;
91
+ private rebasing = false;
66
92
 
67
93
  /**
68
94
  * Track the number of ops which were detected to have a mismatched
@@ -84,10 +110,15 @@ export class Outbox {
84
110
 
85
111
  this.attachFlowBatch = new BatchManager({ hardLimit, softLimit });
86
112
  this.mainBatch = new BatchManager({ hardLimit });
113
+ this.blobAttachBatch = new BatchManager({ hardLimit });
87
114
  }
88
115
 
89
116
  public get isEmpty(): boolean {
90
- return this.attachFlowBatch.length === 0 && this.mainBatch.length === 0;
117
+ return (
118
+ this.attachFlowBatch.length === 0 &&
119
+ this.mainBatch.length === 0 &&
120
+ this.blobAttachBatch.length === 0
121
+ );
91
122
  }
92
123
 
93
124
  /**
@@ -99,9 +130,11 @@ export class Outbox {
99
130
  private maybeFlushPartialBatch() {
100
131
  const mainBatchSeqNums = this.mainBatch.sequenceNumbers;
101
132
  const attachFlowBatchSeqNums = this.attachFlowBatch.sequenceNumbers;
133
+ const blobAttachSeqNums = this.blobAttachBatch.sequenceNumbers;
102
134
  assert(
103
135
  this.params.config.disablePartialFlush ||
104
- sequenceNumbersMatch(mainBatchSeqNums, attachFlowBatchSeqNums),
136
+ (sequenceNumbersMatch(mainBatchSeqNums, attachFlowBatchSeqNums) &&
137
+ sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums)),
105
138
  0x58d /* Reference sequence numbers from both batches must be in sync */,
106
139
  );
107
140
 
@@ -109,7 +142,8 @@ export class Outbox {
109
142
 
110
143
  if (
111
144
  sequenceNumbersMatch(mainBatchSeqNums, currentSequenceNumbers) &&
112
- sequenceNumbersMatch(attachFlowBatchSeqNums, currentSequenceNumbers)
145
+ sequenceNumbersMatch(attachFlowBatchSeqNums, currentSequenceNumbers) &&
146
+ sequenceNumbersMatch(blobAttachSeqNums, currentSequenceNumbers)
113
147
  ) {
114
148
  // The reference sequence numbers are stable, there is nothing to do
115
149
  return;
@@ -124,6 +158,8 @@ export class Outbox {
124
158
  mainClientSequenceNumber: mainBatchSeqNums.clientSequenceNumber,
125
159
  attachReferenceSequenceNumber: attachFlowBatchSeqNums.referenceSequenceNumber,
126
160
  attachClientSequenceNumber: attachFlowBatchSeqNums.clientSequenceNumber,
161
+ blobAttachReferenceSequenceNumber: blobAttachSeqNums.referenceSequenceNumber,
162
+ blobAttachClientSequenceNumber: blobAttachSeqNums.clientSequenceNumber,
127
163
  currentReferenceSequenceNumber: currentSequenceNumbers.referenceSequenceNumber,
128
164
  currentClientSequenceNumber: currentSequenceNumbers.clientSequenceNumber,
129
165
  },
@@ -132,26 +168,14 @@ export class Outbox {
132
168
  }
133
169
 
134
170
  if (!this.params.config.disablePartialFlush) {
135
- this.flush();
171
+ this.flushAll();
136
172
  }
137
173
  }
138
174
 
139
175
  public submit(message: BatchMessage) {
140
176
  this.maybeFlushPartialBatch();
141
177
 
142
- if (
143
- !this.mainBatch.push(
144
- message,
145
- this.params.getCurrentSequenceNumbers().clientSequenceNumber,
146
- )
147
- ) {
148
- throw new GenericError("BatchTooLarge", /* error */ undefined, {
149
- opSize: message.contents?.length ?? 0,
150
- batchSize: this.mainBatch.contentSizeInBytes,
151
- count: this.mainBatch.length,
152
- limit: this.mainBatch.options.hardLimit,
153
- });
154
- }
178
+ this.addMessageToBatchManager(this.mainBatch, message);
155
179
  }
156
180
 
157
181
  public submitAttach(message: BatchMessage) {
@@ -160,26 +184,16 @@ export class Outbox {
160
184
  if (
161
185
  !this.attachFlowBatch.push(
162
186
  message,
187
+ this.isContextReentrant(),
163
188
  this.params.getCurrentSequenceNumbers().clientSequenceNumber,
164
189
  )
165
190
  ) {
166
191
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
167
192
  // when queue is not empty.
168
193
  // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
169
- this.flushInternal(this.attachFlowBatch.popBatch());
170
- if (
171
- !this.attachFlowBatch.push(
172
- message,
173
- this.params.getCurrentSequenceNumbers().clientSequenceNumber,
174
- )
175
- ) {
176
- throw new GenericError("BatchTooLarge", /* error */ undefined, {
177
- opSize: message.contents?.length ?? 0,
178
- batchSize: this.attachFlowBatch.contentSizeInBytes,
179
- count: this.attachFlowBatch.length,
180
- limit: this.attachFlowBatch.options.hardLimit,
181
- });
182
- }
194
+ this.flushInternal(this.attachFlowBatch);
195
+
196
+ this.addMessageToBatchManager(this.attachFlowBatch, message);
183
197
  }
184
198
 
185
199
  // If compression is enabled, we will always successfully receive
@@ -191,23 +205,122 @@ export class Outbox {
191
205
  this.attachFlowBatch.contentSizeInBytes >=
192
206
  this.params.config.compressionOptions.minimumBatchSizeInBytes
193
207
  ) {
194
- this.flushInternal(this.attachFlowBatch.popBatch());
208
+ this.flushInternal(this.attachFlowBatch);
209
+ }
210
+ }
211
+
212
+ public submitBlobAttach(message: BatchMessage) {
213
+ this.maybeFlushPartialBatch();
214
+
215
+ this.addMessageToBatchManager(this.blobAttachBatch, message);
216
+
217
+ // If compression is enabled, we will always successfully receive
218
+ // blobAttach ops and compress then send them at the next JS turn, regardless
219
+ // of the overall size of the accumulated ops in the batch.
220
+ // However, it is more efficient to flush these ops faster, preferably
221
+ // after they reach a size which would benefit from compression.
222
+ if (
223
+ this.blobAttachBatch.contentSizeInBytes >=
224
+ this.params.config.compressionOptions.minimumBatchSizeInBytes
225
+ ) {
226
+ this.flushInternal(this.blobAttachBatch);
227
+ }
228
+ }
229
+
230
+ private addMessageToBatchManager(batchManager: BatchManager, message: BatchMessage) {
231
+ if (
232
+ !batchManager.push(
233
+ message,
234
+ this.isContextReentrant(),
235
+ this.params.getCurrentSequenceNumbers().clientSequenceNumber,
236
+ )
237
+ ) {
238
+ throw new GenericError("BatchTooLarge", /* error */ undefined, {
239
+ opSize: message.contents?.length ?? 0,
240
+ batchSize: batchManager.contentSizeInBytes,
241
+ count: batchManager.length,
242
+ limit: batchManager.options.hardLimit,
243
+ });
195
244
  }
196
245
  }
197
246
 
198
247
  public flush() {
199
- this.flushInternal(this.attachFlowBatch.popBatch());
200
- this.flushInternal(this.mainBatch.popBatch());
248
+ if (this.isContextReentrant()) {
249
+ const error = new UsageError("Flushing is not supported inside DDS event handlers");
250
+ this.params.closeContainer(error);
251
+ throw error;
252
+ }
253
+
254
+ this.flushAll();
201
255
  }
202
256
 
203
- private flushInternal(rawBatch: IBatch) {
204
- const processedBatch = this.compressBatch(rawBatch);
257
+ private flushAll() {
258
+ this.flushInternal(this.attachFlowBatch);
259
+ this.flushInternal(this.blobAttachBatch, true /* disableGroupedBatching */);
260
+ this.flushInternal(this.mainBatch);
261
+ }
262
+
263
+ private flushInternal(batchManager: BatchManager, disableGroupedBatching: boolean = false) {
264
+ if (batchManager.empty) {
265
+ return;
266
+ }
267
+
268
+ const rawBatch = batchManager.popBatch();
269
+ if (rawBatch.hasReentrantOps === true && this.params.config.enableGroupedBatching) {
270
+ assert(!this.rebasing, 0x6fa /* A rebased batch should never have reentrant ops */);
271
+ // If a batch contains reentrant ops (ops created as a result from processing another op)
272
+ // it needs to be rebased so that we can ensure consistent reference sequence numbers
273
+ // and eventual consistency at the DDS level.
274
+ this.rebase(rawBatch, batchManager);
275
+ return;
276
+ }
277
+
278
+ const processedBatch = this.compressBatch(rawBatch, disableGroupedBatching);
205
279
  this.sendBatch(processedBatch);
206
280
 
207
281
  this.persistBatch(rawBatch.content);
208
282
  }
209
283
 
210
- private compressBatch(batch: IBatch): IBatch {
284
+ /**
285
+ * Rebases a batch. All the ops in the batch are resubmitted to the runtime and
286
+ * they will end up back in the same batch manager they were flushed from and subsequently flushed.
287
+ *
288
+ * @param rawBatch - the batch to be rebased
289
+ */
290
+ private rebase(rawBatch: IBatch, batchManager: BatchManager) {
291
+ assert(!this.rebasing, 0x6fb /* Reentrancy */);
292
+
293
+ this.rebasing = true;
294
+ for (const message of rawBatch.content) {
295
+ this.params.reSubmit({
296
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
297
+ content: message.contents!,
298
+ localOpMetadata: message.localOpMetadata,
299
+ opMetadata: message.metadata,
300
+ });
301
+ }
302
+
303
+ if (this.batchRebasesToReport > 0) {
304
+ this.mc.logger.sendTelemetryEvent(
305
+ {
306
+ eventName: "BatchRebase",
307
+ length: rawBatch.content.length,
308
+ referenceSequenceNumber: rawBatch.referenceSequenceNumber,
309
+ },
310
+ new UsageError("BatchRebase"),
311
+ );
312
+ this.batchRebasesToReport--;
313
+ }
314
+
315
+ this.flushInternal(batchManager);
316
+ this.rebasing = false;
317
+ }
318
+
319
+ private isContextReentrant(): boolean {
320
+ return this.params.opReentrancy() && !this.rebasing;
321
+ }
322
+
323
+ private compressBatch(batch: IBatch, disableGroupedBatching: boolean): IBatch {
211
324
  if (
212
325
  batch.content.length === 0 ||
213
326
  this.params.config.compressionOptions === undefined ||
@@ -216,11 +329,11 @@ export class Outbox {
216
329
  this.params.containerContext.submitBatchFn === undefined
217
330
  ) {
218
331
  // Nothing to do if the batch is empty or if compression is disabled or not supported, or if we don't need to compress
219
- return this.params.groupingManager.groupBatch(batch);
332
+ return disableGroupedBatching ? batch : this.params.groupingManager.groupBatch(batch);
220
333
  }
221
334
 
222
335
  const compressedBatch = this.params.compressor.compressBatch(
223
- this.params.groupingManager.groupBatch(batch),
336
+ disableGroupedBatching ? batch : this.params.groupingManager.groupBatch(batch),
224
337
  );
225
338
 
226
339
  if (this.params.splitter.isBatchChunkingEnabled) {
@@ -322,6 +435,7 @@ export class Outbox {
322
435
  return {
323
436
  mainBatch: this.mainBatch.checkpoint(),
324
437
  attachFlowBatch: this.attachFlowBatch.checkpoint(),
438
+ blobAttachBatch: this.blobAttachBatch.checkpoint(),
325
439
  };
326
440
  }
327
441
  }
@@ -120,7 +120,11 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolea
120
120
  }
121
121
 
122
122
  // legacy op format?
123
- if (message.contents.address !== undefined && message.contents.type === undefined) {
123
+ // TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.
124
+ if (
125
+ (message.contents as { address?: unknown }).address !== undefined &&
126
+ (message.contents as { type?: unknown }).type === undefined
127
+ ) {
124
128
  message.type = ContainerMessageType.FluidDataStoreOp;
125
129
  } else {
126
130
  // new format
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.5.1.1";
9
+ export const pkgVersion = "2.0.0-internal.5.3.0";
@@ -4,13 +4,15 @@
4
4
  */
5
5
 
6
6
  import { IDisposable } from "@fluidframework/common-definitions";
7
- import { assert, Lazy } from "@fluidframework/common-utils";
7
+ import { assert } from "@fluidframework/common-utils";
8
8
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
9
9
  import { DataProcessingError } from "@fluidframework/container-utils";
10
+ import { Lazy } from "@fluidframework/core-utils";
10
11
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
11
12
  import Deque from "double-ended-queue";
12
13
  import { ContainerMessageType } from "./containerRuntime";
13
14
  import { pkgVersion } from "./packageVersion";
15
+ import { IBatchMetadata } from "./metadata";
14
16
 
15
17
  /**
16
18
  * ! TODO: Remove this interface in "2.0.0-internal.7.0.0" once we only read IPendingMessageNew
@@ -50,17 +52,19 @@ export interface IPendingLocalState {
50
52
  pendingStates: IPendingState[];
51
53
  }
52
54
 
55
+ export interface IPendingBatchMessage {
56
+ content: string;
57
+ localOpMetadata: unknown;
58
+ opMetadata: Record<string, unknown> | undefined;
59
+ }
60
+
53
61
  export interface IRuntimeStateHandler {
54
62
  connected(): boolean;
55
63
  clientId(): string | undefined;
56
64
  close(error?: ICriticalContainerError): void;
57
65
  applyStashedOp(content: string): Promise<unknown>;
58
- reSubmit(
59
- content: string | undefined,
60
- localOpMetadata: unknown,
61
- opMetadata: Record<string, unknown> | undefined,
62
- ): void;
63
- orderSequentially(callback: () => void): void;
66
+ reSubmit(message: IPendingBatchMessage): void;
67
+ reSubmitBatch(batch: IPendingBatchMessage[]): void;
64
68
  }
65
69
 
66
70
  /**
@@ -207,9 +211,13 @@ export class PendingStateManager implements IDisposable {
207
211
  }
208
212
  }
209
213
 
210
- // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
211
- const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
212
- nextMessage.localOpMetadata = localOpMetadata;
214
+ try {
215
+ // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
216
+ const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
217
+ nextMessage.localOpMetadata = localOpMetadata;
218
+ } catch (error) {
219
+ throw DataProcessingError.wrapIfUnrecognized(error, "applyStashedOp", nextMessage);
220
+ }
213
221
 
214
222
  // then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
215
223
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -262,7 +270,7 @@ export class PendingStateManager implements IDisposable {
262
270
  */
263
271
  private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
264
272
  // This message is the first in a batch if the "batch" property on the metadata is set to true
265
- if (message.metadata?.batch) {
273
+ if ((message.metadata as IBatchMetadata | undefined)?.batch) {
266
274
  // We should not already be processing a batch and there should be no pending batch begin message.
267
275
  assert(
268
276
  !this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,
@@ -290,10 +298,12 @@ export class PendingStateManager implements IDisposable {
290
298
  0x16d /* "There is no pending batch begin message" */,
291
299
  );
292
300
 
293
- const batchEndMetadata = message.metadata?.batch;
301
+ const batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;
294
302
  if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {
295
303
  // Get the batch begin metadata from the first message in the batch.
296
- const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;
304
+ const batchBeginMetadata = (
305
+ this.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined
306
+ )?.batch;
297
307
 
298
308
  // There could be just a single message in the batch. If so, it should not have any batch metadata. If there
299
309
  // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
@@ -311,7 +321,10 @@ export class PendingStateManager implements IDisposable {
311
321
  message,
312
322
  {
313
323
  runtimeVersion: pkgVersion,
314
- batchClientId: this.pendingBatchBeginMessage.clientId,
324
+ batchClientId:
325
+ this.pendingBatchBeginMessage.clientId === null
326
+ ? "null"
327
+ : this.pendingBatchBeginMessage.clientId,
315
328
  clientId: this.stateHandler.clientId(),
316
329
  hasBatchStart: batchBeginMetadata === true,
317
330
  hasBatchEnd: batchEndMetadata === false,
@@ -379,35 +392,37 @@ export class PendingStateManager implements IDisposable {
379
392
  0x554 /* Last pending message cannot be a batch begin */,
380
393
  );
381
394
 
382
- this.stateHandler.orderSequentially(() => {
383
- while (pendingMessagesCount >= 0) {
384
- // check is >= because batch end may be last pending message
385
- this.stateHandler.reSubmit(
386
- pendingMessage.content,
387
- pendingMessage.localOpMetadata,
388
- pendingMessage.opMetadata,
389
- );
390
-
391
- if (pendingMessage.opMetadata?.batch === false) {
392
- break;
393
- }
394
- assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
395
-
396
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
397
- pendingMessage = this.pendingMessages.shift()!;
398
- pendingMessagesCount--;
399
- assert(
400
- pendingMessage.opMetadata?.batch !== true,
401
- 0x556 /* Batch start needs a corresponding batch end */,
402
- );
395
+ const batch: IPendingBatchMessage[] = [];
396
+
397
+ // check is >= because batch end may be last pending message
398
+ while (pendingMessagesCount >= 0) {
399
+ batch.push({
400
+ content: pendingMessage.content,
401
+ localOpMetadata: pendingMessage.localOpMetadata,
402
+ opMetadata: pendingMessage.opMetadata,
403
+ });
404
+
405
+ if (pendingMessage.opMetadata?.batch === false) {
406
+ break;
403
407
  }
404
- });
408
+ assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
409
+
410
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
411
+ pendingMessage = this.pendingMessages.shift()!;
412
+ pendingMessagesCount--;
413
+ assert(
414
+ pendingMessage.opMetadata?.batch !== true,
415
+ 0x556 /* Batch start needs a corresponding batch end */,
416
+ );
417
+ }
418
+
419
+ this.stateHandler.reSubmitBatch(batch);
405
420
  } else {
406
- this.stateHandler.reSubmit(
407
- pendingMessage.content,
408
- pendingMessage.localOpMetadata,
409
- pendingMessage.opMetadata,
410
- );
421
+ this.stateHandler.reSubmit({
422
+ content: pendingMessage.content,
423
+ localOpMetadata: pendingMessage.localOpMetadata,
424
+ opMetadata: pendingMessage.opMetadata,
425
+ });
411
426
  }
412
427
  }
413
428
  }
@@ -15,6 +15,7 @@ import {
15
15
  } from "@fluidframework/container-utils";
16
16
  import { DeltaScheduler } from "./deltaScheduler";
17
17
  import { pkgVersion } from "./packageVersion";
18
+ import { IBatchMetadata } from "./metadata";
18
19
 
19
20
  type IRuntimeMessageMetadata =
20
21
  | undefined
@@ -61,7 +62,9 @@ export class ScheduleManager {
61
62
  this.deltaScheduler.batchBegin(message);
62
63
 
63
64
  const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
64
- this.batchClientId = batch ? message.clientId : undefined;
65
+ // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
66
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
67
+ this.batchClientId = batch ? (message.clientId as string) : undefined;
65
68
  }
66
69
  }
67
70
 
@@ -127,7 +130,9 @@ class ScheduleManagerCore {
127
130
 
128
131
  // Set the batch flag to false on the last message to indicate the end of the send batch
129
132
  const lastMessage = messages[messages.length - 1];
130
- lastMessage.metadata = { ...lastMessage.metadata, batch: false };
133
+ // TODO: It's not clear if this shallow clone is required, as opposed to just setting "batch" to false.
134
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
135
+ lastMessage.metadata = { ...(lastMessage.metadata as any), batch: false };
131
136
  });
132
137
 
133
138
  // Listen for updates and peek at the inbound
@@ -185,7 +190,7 @@ class ScheduleManagerCore {
185
190
  {
186
191
  type: message.type,
187
192
  contentType: typeof message.contents,
188
- batch: message.metadata?.batch,
193
+ batch: (message.metadata as IBatchMetadata | undefined)?.batch,
189
194
  compression: message.compression,
190
195
  pauseSeqNum: this.pauseSequenceNumber,
191
196
  },
@@ -263,7 +268,8 @@ class ScheduleManagerCore {
263
268
  message,
264
269
  {
265
270
  runtimeVersion: pkgVersion,
266
- batchClientId: this.currentBatchClientId,
271
+ batchClientId:
272
+ this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
267
273
  pauseSequenceNumber: this.pauseSequenceNumber,
268
274
  localBatch: this.currentBatchClientId === this.getClientId(),
269
275
  messageType: message.type,
@@ -297,7 +303,8 @@ class ScheduleManagerCore {
297
303
  ) {
298
304
  throw new DataCorruptionError("OpBatchIncomplete", {
299
305
  runtimeVersion: pkgVersion,
300
- batchClientId: this.currentBatchClientId,
306
+ batchClientId:
307
+ this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
301
308
  pauseSequenceNumber: this.pauseSequenceNumber,
302
309
  localBatch: this.currentBatchClientId === this.getClientId(),
303
310
  localMessage: message.clientId === this.getClientId(),
@@ -321,7 +328,9 @@ class ScheduleManagerCore {
321
328
  0x29f /* "we should be processing ops when there is no active batch" */,
322
329
  );
323
330
  this.pauseSequenceNumber = message.sequenceNumber;
324
- this.currentBatchClientId = message.clientId;
331
+ // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
332
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
333
+ this.currentBatchClientId = message.clientId as string;
325
334
  // Start of the batch
326
335
  // Only pause processing if queue has no other ops!
327
336
  // If there are any other ops in the queue, processing will be stopped when they are processed!
@@ -63,6 +63,7 @@ export {
63
63
  IUploadSummaryResult,
64
64
  SummarizeResultPart,
65
65
  SubmitSummaryFailureData,
66
+ SummaryStage,
66
67
  } from "./summarizerTypes";
67
68
  export {
68
69
  IAckedSummary,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable } from "@fluidframework/common-definitions";
6
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
7
  import {
8
8
  ITelemetryLoggerExt,
9
9
  ChildLogger,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryErrorEvent } from "@fluidframework/common-definitions";
6
+ import { ITelemetryErrorEvent } from "@fluidframework/core-interfaces";
7
7
  import {
8
8
  ISummarizerNode,
9
9
  ISummarizerNodeConfig,
@@ -243,9 +243,9 @@ export class SummarizerNode implements IRootSummarizerNode {
243
243
  private wasSummarizeMissed(parentSkipRecursion: boolean): boolean {
244
244
  assert(
245
245
  this.wipSummaryLogger !== undefined,
246
- "wipSummaryLogger should have been set in startSummary or ctor",
246
+ 0x6fc /* wipSummaryLogger should have been set in startSummary or ctor */,
247
247
  );
248
- assert(this.wipReferenceSequenceNumber !== undefined, "Not tracking a summary");
248
+ assert(this.wipReferenceSequenceNumber !== undefined, 0x6fd /* Not tracking a summary */);
249
249
 
250
250
  // If the parent node skipped recursion, it did not call summarize on this node. So, summarize was not missed
251
251
  // but was intentionally not called.
@@ -335,7 +335,7 @@ export class SummarizerNode implements IRootSummarizerNode {
335
335
 
336
336
  // If localPathsToUse is undefined, it means summarize didn't run for this node and in that case the validate
337
337
  // step should have failed.
338
- assert(localPathsToUse !== undefined, "summarize didn't run for node");
338
+ assert(localPathsToUse !== undefined, 0x6fe /* summarize didn't run for node */);
339
339
  const summary = new SummaryNode({
340
340
  ...localPathsToUse,
341
341
  referenceSequenceNumber: this.wipReferenceSequenceNumber,
@@ -8,7 +8,8 @@ import {
8
8
  LoggingError,
9
9
  TelemetryDataTag,
10
10
  } from "@fluidframework/telemetry-utils";
11
- import { assert, LazyPromise } from "@fluidframework/common-utils";
11
+ import { assert } from "@fluidframework/common-utils";
12
+ import { LazyPromise } from "@fluidframework/core-utils";
12
13
  import { ISnapshotTree } from "@fluidframework/protocol-definitions";
13
14
  import {
14
15
  CreateChildSummarizerNodeParam,
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IEvent, IEventProvider, ITelemetryProperties } from "@fluidframework/common-definitions";
6
+ import { IEvent, IEventProvider } from "@fluidframework/common-definitions";
7
+ import { ITelemetryProperties } from "@fluidframework/core-interfaces";
7
8
  import { ITelemetryLoggerExt, ITelemetryLoggerPropertyBag } from "@fluidframework/telemetry-utils";
8
9
  import { ContainerWarning, IDeltaManager } from "@fluidframework/container-definitions";
9
10
  import {