@fluidframework/container-runtime 2.0.0-internal.5.1.0 → 2.0.0-internal.5.2.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 (183) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/containerRuntime.d.ts +22 -0
  3. package/dist/containerRuntime.d.ts.map +1 -1
  4. package/dist/containerRuntime.js +54 -10
  5. package/dist/containerRuntime.js.map +1 -1
  6. package/dist/dataStoreContext.d.ts +1 -2
  7. package/dist/dataStoreContext.d.ts.map +1 -1
  8. package/dist/dataStoreContext.js.map +1 -1
  9. package/dist/dataStoreContexts.d.ts +2 -1
  10. package/dist/dataStoreContexts.d.ts.map +1 -1
  11. package/dist/dataStoreContexts.js.map +1 -1
  12. package/dist/dataStores.d.ts +2 -1
  13. package/dist/dataStores.d.ts.map +1 -1
  14. package/dist/dataStores.js.map +1 -1
  15. package/dist/gc/gcDefinitions.d.ts +0 -1
  16. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  17. package/dist/gc/gcDefinitions.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/opLifecycle/batchManager.d.ts +2 -1
  22. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  23. package/dist/opLifecycle/batchManager.js +5 -1
  24. package/dist/opLifecycle/batchManager.js.map +1 -1
  25. package/dist/opLifecycle/definitions.d.ts +11 -0
  26. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  27. package/dist/opLifecycle/definitions.js.map +1 -1
  28. package/dist/opLifecycle/index.d.ts +1 -1
  29. package/dist/opLifecycle/index.d.ts.map +1 -1
  30. package/dist/opLifecycle/index.js +2 -1
  31. package/dist/opLifecycle/index.js.map +1 -1
  32. package/dist/opLifecycle/outbox.d.ts +29 -2
  33. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  34. package/dist/opLifecycle/outbox.js +87 -19
  35. package/dist/opLifecycle/outbox.js.map +1 -1
  36. package/dist/packageVersion.d.ts +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/dist/pendingStateManager.d.ts +8 -3
  40. package/dist/pendingStateManager.d.ts.map +1 -1
  41. package/dist/pendingStateManager.js +23 -16
  42. package/dist/pendingStateManager.js.map +1 -1
  43. package/dist/summary/index.d.ts +2 -2
  44. package/dist/summary/index.d.ts.map +1 -1
  45. package/dist/summary/index.js +2 -1
  46. package/dist/summary/index.js.map +1 -1
  47. package/dist/summary/runningSummarizer.d.ts +1 -1
  48. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  49. package/dist/summary/runningSummarizer.js.map +1 -1
  50. package/dist/summary/summarizerNode/index.d.ts +1 -1
  51. package/dist/summary/summarizerNode/index.d.ts.map +1 -1
  52. package/dist/summary/summarizerNode/index.js.map +1 -1
  53. package/dist/summary/summarizerNode/summarizerNode.d.ts +32 -6
  54. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  55. package/dist/summary/summarizerNode/summarizerNode.js +90 -22
  56. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  57. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  58. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  59. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  60. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  61. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  62. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +56 -20
  63. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  64. package/dist/summary/summarizerTypes.d.ts +7 -1
  65. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  66. package/dist/summary/summarizerTypes.js.map +1 -1
  67. package/dist/summary/summaryCollection.d.ts +2 -1
  68. package/dist/summary/summaryCollection.d.ts.map +1 -1
  69. package/dist/summary/summaryCollection.js.map +1 -1
  70. package/dist/summary/summaryFormat.d.ts +1 -0
  71. package/dist/summary/summaryFormat.d.ts.map +1 -1
  72. package/dist/summary/summaryFormat.js +2 -1
  73. package/dist/summary/summaryFormat.js.map +1 -1
  74. package/dist/summary/summaryGenerator.d.ts +13 -4
  75. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  76. package/dist/summary/summaryGenerator.js +22 -8
  77. package/dist/summary/summaryGenerator.js.map +1 -1
  78. package/dist/summary/summaryManager.d.ts +2 -1
  79. package/dist/summary/summaryManager.d.ts.map +1 -1
  80. package/dist/summary/summaryManager.js.map +1 -1
  81. package/lib/containerRuntime.d.ts +22 -0
  82. package/lib/containerRuntime.d.ts.map +1 -1
  83. package/lib/containerRuntime.js +57 -13
  84. package/lib/containerRuntime.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +1 -2
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js.map +1 -1
  88. package/lib/dataStoreContexts.d.ts +2 -1
  89. package/lib/dataStoreContexts.d.ts.map +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +2 -1
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js.map +1 -1
  94. package/lib/gc/gcDefinitions.d.ts +0 -1
  95. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  96. package/lib/gc/gcDefinitions.js.map +1 -1
  97. package/lib/index.d.ts +1 -1
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js.map +1 -1
  100. package/lib/opLifecycle/batchManager.d.ts +2 -1
  101. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  102. package/lib/opLifecycle/batchManager.js +5 -1
  103. package/lib/opLifecycle/batchManager.js.map +1 -1
  104. package/lib/opLifecycle/definitions.d.ts +11 -0
  105. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  106. package/lib/opLifecycle/definitions.js.map +1 -1
  107. package/lib/opLifecycle/index.d.ts +1 -1
  108. package/lib/opLifecycle/index.d.ts.map +1 -1
  109. package/lib/opLifecycle/index.js +1 -1
  110. package/lib/opLifecycle/index.js.map +1 -1
  111. package/lib/opLifecycle/outbox.d.ts +29 -2
  112. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  113. package/lib/opLifecycle/outbox.js +85 -18
  114. package/lib/opLifecycle/outbox.js.map +1 -1
  115. package/lib/packageVersion.d.ts +1 -1
  116. package/lib/packageVersion.js +1 -1
  117. package/lib/packageVersion.js.map +1 -1
  118. package/lib/pendingStateManager.d.ts +8 -3
  119. package/lib/pendingStateManager.d.ts.map +1 -1
  120. package/lib/pendingStateManager.js +23 -16
  121. package/lib/pendingStateManager.js.map +1 -1
  122. package/lib/summary/index.d.ts +2 -2
  123. package/lib/summary/index.d.ts.map +1 -1
  124. package/lib/summary/index.js +1 -1
  125. package/lib/summary/index.js.map +1 -1
  126. package/lib/summary/runningSummarizer.d.ts +1 -1
  127. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  128. package/lib/summary/runningSummarizer.js.map +1 -1
  129. package/lib/summary/summarizerNode/index.d.ts +1 -1
  130. package/lib/summary/summarizerNode/index.d.ts.map +1 -1
  131. package/lib/summary/summarizerNode/index.js.map +1 -1
  132. package/lib/summary/summarizerNode/summarizerNode.d.ts +32 -6
  133. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  134. package/lib/summary/summarizerNode/summarizerNode.js +90 -22
  135. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  136. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  137. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  138. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  139. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  140. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  141. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +56 -20
  142. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  143. package/lib/summary/summarizerTypes.d.ts +7 -1
  144. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  145. package/lib/summary/summarizerTypes.js.map +1 -1
  146. package/lib/summary/summaryCollection.d.ts +2 -1
  147. package/lib/summary/summaryCollection.d.ts.map +1 -1
  148. package/lib/summary/summaryCollection.js.map +1 -1
  149. package/lib/summary/summaryFormat.d.ts +1 -0
  150. package/lib/summary/summaryFormat.d.ts.map +1 -1
  151. package/lib/summary/summaryFormat.js +2 -1
  152. package/lib/summary/summaryFormat.js.map +1 -1
  153. package/lib/summary/summaryGenerator.d.ts +13 -4
  154. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  155. package/lib/summary/summaryGenerator.js +20 -7
  156. package/lib/summary/summaryGenerator.js.map +1 -1
  157. package/lib/summary/summaryManager.d.ts +2 -1
  158. package/lib/summary/summaryManager.d.ts.map +1 -1
  159. package/lib/summary/summaryManager.js.map +1 -1
  160. package/package.json +17 -17
  161. package/src/containerRuntime.ts +82 -14
  162. package/src/dataStoreContext.ts +8 -2
  163. package/src/dataStoreContexts.ts +2 -1
  164. package/src/dataStores.ts +2 -2
  165. package/src/gc/gcDefinitions.ts +0 -1
  166. package/src/index.ts +2 -0
  167. package/src/opLifecycle/batchManager.ts +9 -1
  168. package/src/opLifecycle/definitions.ts +11 -0
  169. package/src/opLifecycle/index.ts +1 -1
  170. package/src/opLifecycle/outbox.ts +107 -16
  171. package/src/packageVersion.ts +1 -1
  172. package/src/pendingStateManager.ts +38 -34
  173. package/src/summary/index.ts +3 -1
  174. package/src/summary/runningSummarizer.ts +1 -1
  175. package/src/summary/summarizerNode/index.ts +1 -0
  176. package/src/summary/summarizerNode/summarizerNode.ts +107 -25
  177. package/src/summary/summarizerNode/summarizerNodeUtils.ts +25 -2
  178. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +61 -19
  179. package/src/summary/summarizerTypes.ts +10 -1
  180. package/src/summary/summaryCollection.ts +2 -1
  181. package/src/summary/summaryFormat.ts +5 -1
  182. package/src/summary/summaryGenerator.ts +31 -8
  183. package/src/summary/summaryManager.ts +2 -1
@@ -62,6 +62,8 @@ export {
62
62
  ISummarizingWarning,
63
63
  IUploadSummaryResult,
64
64
  SummarizeResultPart,
65
+ SubmitSummaryFailureData,
66
+ SummaryStage,
65
67
  } from "./summarizerTypes";
66
68
  export {
67
69
  IAckedSummary,
@@ -96,7 +98,7 @@ export {
96
98
  wrapSummaryInChannelsTree,
97
99
  idCompressorBlobName,
98
100
  } from "./summaryFormat";
99
- export { getFailMessage, SummarizeReason } from "./summaryGenerator";
101
+ export { getFailMessage, RetriableSummaryError, SummarizeReason } from "./summaryGenerator";
100
102
  export {
101
103
  IConnectedEvents,
102
104
  IConnectedState,
@@ -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,
@@ -7,6 +7,7 @@ export {
7
7
  IFetchSnapshotResult,
8
8
  ISummarizerNodeRootContract,
9
9
  RefreshSummaryResult,
10
+ ValidateSummaryResult,
10
11
  } from "./summarizerNodeUtils";
11
12
  export { IRootSummarizerNode, createRootSummarizerNode } from "./summarizerNode";
12
13
  export { IRootSummarizerNodeWithGC, createRootSummarizerNodeWithGC } from "./summarizerNodeWithGc";
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { ITelemetryErrorEvent } from "@fluidframework/common-definitions";
6
7
  import {
7
8
  ISummarizerNode,
8
9
  ISummarizerNodeConfig,
@@ -20,7 +21,6 @@ import {
20
21
  ISnapshotTree,
21
22
  SummaryObject,
22
23
  } from "@fluidframework/protocol-definitions";
23
- import { ITelemetryErrorEvent } from "@fluidframework/common-definitions";
24
24
  import {
25
25
  ITelemetryLoggerExt,
26
26
  ChildLogger,
@@ -45,6 +45,7 @@ import {
45
45
  parseSummaryTreeForSubtrees,
46
46
  RefreshSummaryResult,
47
47
  SummaryNode,
48
+ ValidateSummaryResult,
48
49
  } from "./summarizerNodeUtils";
49
50
 
50
51
  export interface IRootSummarizerNode extends ISummarizerNode, ISummarizerNodeRootContract {}
@@ -193,24 +194,117 @@ export class SummarizerNode implements IRootSummarizerNode {
193
194
  }
194
195
 
195
196
  /**
196
- * Complete the WIP summary for the given proposalHandle
197
+ * Validates that the in-progress summary is correct, i.e., summarize should have run for all non-skipped
198
+ * nodes. This will only be called for the root summarizer node and is called by it recursively on all child nodes.
199
+ *
200
+ * @returns ValidateSummaryResult which contains a boolean success indicating whether the validation was successful.
201
+ * In case of failure, additional information is returned indicating type of failure and where it was.
202
+ */
203
+ public validateSummary(): ValidateSummaryResult {
204
+ return this.validateSummaryCore(false /* parentSkipRecursion */);
205
+ }
206
+
207
+ /**
208
+ * Validates that the in-progress summary is correct for all nodes, i.e., summarize should have run for all
209
+ * non-skipped nodes.
210
+ * @param parentSkipRecursion - true if the parent of this node skipped recursing the child nodes when summarizing.
211
+ * In that case, the children will not have work-in-progress state.
212
+ *
213
+ * @returns ValidateSummaryResult which contains a boolean success indicating whether the validation was successful.
214
+ * In case of failure, additional information is returned indicating type of failure and where it was.
215
+ */
216
+ protected validateSummaryCore(parentSkipRecursion: boolean): ValidateSummaryResult {
217
+ if (this.wasSummarizeMissed(parentSkipRecursion)) {
218
+ return {
219
+ success: false,
220
+ reason: "NodeDidNotSummarize",
221
+ id: {
222
+ tag: TelemetryDataTag.CodeArtifact,
223
+ value: this.telemetryNodeId,
224
+ },
225
+ // These errors are usually transient and should go away when summarize is retried.
226
+ retryAfterSeconds: 1,
227
+ };
228
+ }
229
+ if (parentSkipRecursion) {
230
+ return { success: true };
231
+ }
232
+
233
+ for (const child of this.children.values()) {
234
+ const result = child.validateSummaryCore(this.wipSkipRecursion || parentSkipRecursion);
235
+ // If any child fails, return the failure.
236
+ if (!result.success) {
237
+ return result;
238
+ }
239
+ }
240
+ return { success: true };
241
+ }
242
+
243
+ private wasSummarizeMissed(parentSkipRecursion: boolean): boolean {
244
+ assert(
245
+ this.wipSummaryLogger !== undefined,
246
+ 0x6fc /* wipSummaryLogger should have been set in startSummary or ctor */,
247
+ );
248
+ assert(this.wipReferenceSequenceNumber !== undefined, 0x6fd /* Not tracking a summary */);
249
+
250
+ // If the parent node skipped recursion, it did not call summarize on this node. So, summarize was not missed
251
+ // but was intentionally not called.
252
+ // Otherwise, summarize should have been called on this node and wipLocalPaths must be set.
253
+ if (parentSkipRecursion || this.wipLocalPaths !== undefined) {
254
+ return false;
255
+ }
256
+
257
+ /**
258
+ * The absence of wip local path indicates that summarize was not called for this node. Return failure.
259
+ * This can happen if:
260
+ * 1. A child node was created after summarize was already called on the parent. For example, a data store
261
+ * is realized (loaded) after summarize was called on it creating summarizer nodes for its DDSes. In this case,
262
+ * parentSkipRecursion will be true and the if block above would handle it.
263
+ * 2. A new node was created but summarize was never called on it. This can mean that the summary that is
264
+ * generated may not have the data from this node. We should not continue, log and throw an error. This
265
+ * will help us identify these cases and take appropriate action.
266
+ *
267
+ * This happens due to scenarios such as data store created during summarize. Such errors should go away when
268
+ * summarize is attempted again.
269
+ */
270
+ return true;
271
+ }
272
+
273
+ /**
274
+ * Called after summary has been uploaded to the server. Add the work-in-progress state to the pending summary
275
+ * queue. We track this until we get an ack from the server for this summary.
276
+ * @param proposalHandle - The handle of the summary that was uploaded to the server.
197
277
  */
198
- public completeSummary(proposalHandle: string) {
199
- this.completeSummaryCore(proposalHandle, undefined, false);
278
+ public completeSummary(proposalHandle: string, validate: boolean) {
279
+ this.completeSummaryCore(
280
+ proposalHandle,
281
+ undefined /* parentPath */,
282
+ false /* parentSkipRecursion */,
283
+ validate,
284
+ );
200
285
  }
201
286
 
202
287
  /**
203
- * Recursive implementation for completeSummary, with additional internal-only parameters
288
+ * Recursive implementation for completeSummary, with additional internal-only parameters.
289
+ * @param proposalHandle - The handle of the summary that was uploaded to the server.
290
+ * @param parentPath - The path of the parent node which is used to build the path of this node.
291
+ * @param parentSkipRecursion - true if the parent of this node skipped recursing the child nodes when summarizing.
292
+ * In that case, the children will not have work-in-progress state.
293
+ * @param validate - true to validate that the in-progress summary is correct for all nodes.
204
294
  */
205
295
  protected completeSummaryCore(
206
296
  proposalHandle: string,
207
297
  parentPath: EscapedPath | undefined,
208
298
  parentSkipRecursion: boolean,
299
+ validate: boolean,
209
300
  ) {
210
- assert(
211
- this.wipSummaryLogger !== undefined,
212
- 0x1a3 /* "wipSummaryLogger should have been set in startSummary or ctor" */,
213
- );
301
+ if (validate && this.wasSummarizeMissed(parentSkipRecursion)) {
302
+ this.throwUnexpectedError({
303
+ eventName: "NodeDidNotSummarize",
304
+ proposalHandle,
305
+ });
306
+ }
307
+
214
308
  assert(this.wipReferenceSequenceNumber !== undefined, 0x1a4 /* "Not tracking a summary" */);
215
309
  let localPathsToUse = this.wipLocalPaths;
216
310
 
@@ -239,22 +333,9 @@ export class SummarizerNode implements IRootSummarizerNode {
239
333
  }
240
334
  }
241
335
 
242
- /**
243
- * The absence of wip local path indicates that summarize was not called for this node. This can happen if:
244
- * 1. A child node was created after summarize was already called on the parent. For example, a data store
245
- * is realized (loaded) after summarize was called on it creating summarizer nodes for its DDSes. In this case,
246
- * parentSkipRecursion will be true and the if block above would handle it.
247
- * 2. A new node was created but summarize was never called on it. This can mean that the summary that is
248
- * generated may not have the data from this node. We should not continue, log and throw an error. This
249
- * will help us identify these cases and take appropriate action.
250
- */
251
- if (localPathsToUse === undefined) {
252
- this.throwUnexpectedError({
253
- eventName: "NodeNotSummarized",
254
- proposalHandle,
255
- });
256
- }
257
-
336
+ // If localPathsToUse is undefined, it means summarize didn't run for this node and in that case the validate
337
+ // step should have failed.
338
+ assert(localPathsToUse !== undefined, 0x6fe /* summarize didn't run for node */);
258
339
  const summary = new SummaryNode({
259
340
  ...localPathsToUse,
260
341
  referenceSequenceNumber: this.wipReferenceSequenceNumber,
@@ -266,6 +347,7 @@ export class SummarizerNode implements IRootSummarizerNode {
266
347
  proposalHandle,
267
348
  fullPathForChildren,
268
349
  this.wipSkipRecursion || parentSkipRecursion,
350
+ validate,
269
351
  );
270
352
  }
271
353
  // Note that this overwrites existing pending summary with
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
6
+ import { ITelemetryLoggerExt, TelemetryDataTag } from "@fluidframework/telemetry-utils";
7
7
  import { ISnapshotTree, ISummaryTree, SummaryObject } from "@fluidframework/protocol-definitions";
8
8
  import { channelsTreeName, ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
9
9
  import { ReadAndParseBlob } from "@fluidframework/runtime-utils";
@@ -42,9 +42,32 @@ export interface IFetchSnapshotResult {
42
42
  snapshotRefSeq: number;
43
43
  }
44
44
 
45
+ /**
46
+ * Return type of validateSummary function. In case of success, the object returned should have success: true.
47
+ * In case of failure, the object returned should have success: false and additional properties to indicate what
48
+ * the failure was, where it was, can it be retried, etc.
49
+ */
50
+ export type ValidateSummaryResult =
51
+ | {
52
+ success: true;
53
+ }
54
+ | {
55
+ success: false;
56
+ /** The failure reason */
57
+ reason: string;
58
+ /** id of the node that failed during validation */
59
+ id: {
60
+ tag: TelemetryDataTag.CodeArtifact;
61
+ value: string | undefined;
62
+ };
63
+ /** If the error can be retried, time to wait before retrying */
64
+ retryAfterSeconds?: number;
65
+ };
66
+
45
67
  export interface ISummarizerNodeRootContract {
46
68
  startSummary(referenceSequenceNumber: number, summaryLogger: ITelemetryLoggerExt): void;
47
- completeSummary(proposalHandle: string): void;
69
+ validateSummary(): ValidateSummaryResult;
70
+ completeSummary(proposalHandle: string, validate: boolean): void;
48
71
  clearSummary(): void;
49
72
  refreshLatestSummary(
50
73
  proposalHandle: string | undefined,
@@ -38,6 +38,7 @@ import {
38
38
  ISummarizerNodeRootContract,
39
39
  parseSummaryForSubtrees,
40
40
  SummaryNode,
41
+ ValidateSummaryResult,
41
42
  } from "./summarizerNodeUtils";
42
43
 
43
44
  export interface IRootSummarizerNodeWithGC
@@ -47,7 +48,7 @@ export interface IRootSummarizerNodeWithGC
47
48
  // Extend SummaryNode to add used routes tracking to it.
48
49
  class SummaryNodeWithGC extends SummaryNode {
49
50
  constructor(
50
- public readonly serializedUsedRoutes: string,
51
+ public readonly serializedUsedRoutes: string | undefined,
51
52
  summary: {
52
53
  readonly referenceSequenceNumber: number;
53
54
  readonly basePath: EscapedPath | undefined;
@@ -255,45 +256,86 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
255
256
  super.startSummary(referenceSequenceNumber, summaryLogger);
256
257
  }
257
258
 
259
+ /**
260
+ * Validates that the in-progress summary is correct for all nodes, i.e., GC should have run for non-skipped nodes.
261
+ * @param parentSkipRecursion - true if the parent of this node skipped recursing the child nodes when running GC.
262
+ * In that case, the children will not have work-in-progress state.
263
+ *
264
+ * @returns ValidateSummaryResult which contains a boolean success indicating whether the validation was successful.
265
+ * In case of failure, additional information is returned indicating type of failure and where it was.
266
+ */
267
+ protected validateSummaryCore(parentSkipRecursion: boolean): ValidateSummaryResult {
268
+ if (this.wasGCMissed()) {
269
+ return {
270
+ success: false,
271
+ reason: "NodeDidNotRunGC",
272
+ id: {
273
+ tag: TelemetryDataTag.CodeArtifact,
274
+ value: this.telemetryNodeId,
275
+ },
276
+ // These errors are usually transient and should go away when summarize / GC is retried.
277
+ retryAfterSeconds: 1,
278
+ };
279
+ }
280
+ return super.validateSummaryCore(parentSkipRecursion);
281
+ }
282
+
283
+ private wasGCMissed(): boolean {
284
+ // If GC is disabled, it should not have run so it was not missed.
285
+ // Otherwise, GC should have been called on this node and wipSerializedUsedRoutes must be set.
286
+ if (this.gcDisabled || this.wipSerializedUsedRoutes !== undefined) {
287
+ return false;
288
+ }
289
+ /**
290
+ * The absence of wip used routes indicates that GC was not run on this node. This can happen if:
291
+ * 1. A child node was created after GC was already run on the parent. For example, a data store
292
+ * is realized (loaded) after GC was run on it creating summarizer nodes for its DDSes. In this
293
+ * case, the parent will pass on used routes to the child nodes and it will have wip used routes.
294
+ * 2. A new node was created but GC was never run on it. This can mean that the GC data generated
295
+ * during summarize is incomplete.
296
+ *
297
+ * This happens due to scenarios such as data store created during summarize. Such errors should go away when
298
+ * summarize is attempted again.
299
+ */
300
+ return true;
301
+ }
302
+
258
303
  /**
259
304
  * Called after summary has been uploaded to the server. Add the work-in-progress state to the pending
260
305
  * summary queue. We track this until we get an ack from the server for this summary.
306
+ * @param proposalHandle - The handle of the summary that was uploaded to the server.
307
+ * @param parentPath - The path of the parent node which is used to build the path of this node.
308
+ * @param parentSkipRecursion - true if the parent of this node skipped recursing the child nodes when summarizing.
309
+ * In that case, the children will not have work-in-progress state.
310
+ * @param validate - true to validate that the in-progress summary is correct for all nodes.
261
311
  */
262
312
  protected completeSummaryCore(
263
313
  proposalHandle: string,
264
314
  parentPath: EscapedPath | undefined,
265
315
  parentSkipRecursion: boolean,
316
+ validate: boolean,
266
317
  ) {
318
+ if (validate && this.wasGCMissed()) {
319
+ this.throwUnexpectedError({
320
+ eventName: "NodeDidNotRunGC",
321
+ proposalHandle,
322
+ });
323
+ }
324
+
267
325
  let wipSerializedUsedRoutes: string | undefined;
268
326
  // If GC is disabled, don't set wip used routes.
269
327
  if (!this.gcDisabled) {
270
328
  wipSerializedUsedRoutes = this.wipSerializedUsedRoutes;
271
- /**
272
- * The absence of wip used routes indicates that GC was not run on this node. This can happen if:
273
- * 1. A child node was created after GC was already run on the parent. For example, a data store
274
- * is realized (loaded) after GC was run on it creating summarizer nodes for its DDSes. In this
275
- * case, the used routes of the parent should be passed on the child nodes and it should be fine.
276
- * 2. A new node was created but GC was never run on it. This can mean that the GC data generated
277
- * during summarize is complete . We should not continue, log and throw an error. This will help us
278
- * identify these cases and take appropriate action.
279
- */
280
- if (wipSerializedUsedRoutes === undefined) {
281
- this.throwUnexpectedError({
282
- eventName: "NodeDidNotRunGC",
283
- proposalHandle,
284
- });
285
- }
286
329
  }
287
330
 
288
- super.completeSummaryCore(proposalHandle, parentPath, parentSkipRecursion);
331
+ super.completeSummaryCore(proposalHandle, parentPath, parentSkipRecursion, validate);
289
332
 
290
333
  // If GC is disabled, skip setting pending summary with GC state.
291
334
  if (!this.gcDisabled) {
292
335
  const summaryNode = this.pendingSummaries.get(proposalHandle);
293
336
  if (summaryNode !== undefined) {
294
337
  const summaryNodeWithGC = new SummaryNodeWithGC(
295
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
296
- wipSerializedUsedRoutes!,
338
+ wipSerializedUsedRoutes,
297
339
  summaryNode,
298
340
  );
299
341
  this.pendingSummaries.set(proposalHandle, summaryNodeWithGC);
@@ -213,6 +213,13 @@ export type SubmitSummaryResult =
213
213
  | IUploadSummaryResult
214
214
  | ISubmitSummaryOpResult;
215
215
 
216
+ /** The stages of Summarize, used to describe how far progress succeeded in case of a failure at a later stage. */
217
+ export type SummaryStage = SubmitSummaryResult["stage"] | "unknown";
218
+ /** The data in summarizer result when submit summary stage fails. */
219
+ export interface SubmitSummaryFailureData {
220
+ stage: SummaryStage;
221
+ }
222
+
216
223
  export interface IBroadcastSummaryResult {
217
224
  readonly summarizeOp: ISummaryOpMessage;
218
225
  readonly broadcastDuration: number;
@@ -243,7 +250,9 @@ export type SummarizeResultPart<TSuccess, TFailure = undefined> =
243
250
 
244
251
  export interface ISummarizeResults {
245
252
  /** Resolves when we generate, upload, and submit the summary. */
246
- readonly summarySubmitted: Promise<SummarizeResultPart<SubmitSummaryResult>>;
253
+ readonly summarySubmitted: Promise<
254
+ SummarizeResultPart<SubmitSummaryResult, SubmitSummaryFailureData>
255
+ >;
247
256
  /** Resolves when we observe our summarize op broadcast. */
248
257
  readonly summaryOpBroadcasted: Promise<SummarizeResultPart<IBroadcastSummaryResult>>;
249
258
  /** Resolves when we receive a summaryAck or summaryNack. */
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, IEvent } from "@fluidframework/common-definitions";
6
+ import { IEvent } from "@fluidframework/common-definitions";
7
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
8
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
8
9
  import { Deferred, assert, TypedEventEmitter } from "@fluidframework/common-utils";
9
10
  import { IDeltaManager } from "@fluidframework/container-definitions";
@@ -5,7 +5,10 @@
5
5
 
6
6
  import { assert } from "@fluidframework/common-utils";
7
7
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
8
- import { readAndParse } from "@fluidframework/driver-utils";
8
+ import {
9
+ readAndParse,
10
+ blobHeadersBlobName as blobNameForBlobHeaders,
11
+ } from "@fluidframework/driver-utils";
9
12
  import {
10
13
  ISequencedDocumentMessage,
11
14
  ISnapshotTree,
@@ -153,6 +156,7 @@ export const chunksBlobName = ".chunks";
153
156
  export const electedSummarizerBlobName = ".electedSummarizer";
154
157
  export const blobsTreeName = ".blobs";
155
158
  export const idCompressorBlobName = ".idCompressor";
159
+ export const blobHeadersBlobName = blobNameForBlobHeaders;
156
160
 
157
161
  export function rootHasIsolatedChannels(metadata?: IContainerRuntimeMetadata): boolean {
158
162
  return !!metadata && !metadata.disableIsolatedChannels;
@@ -9,6 +9,8 @@ import {
9
9
  LoggingError,
10
10
  ChildLogger,
11
11
  } from "@fluidframework/telemetry-utils";
12
+ import { ITelemetryProperties } from "@fluidframework/common-definitions";
13
+
12
14
  import {
13
15
  assert,
14
16
  Deferred,
@@ -32,6 +34,8 @@ import {
32
34
  ISummaryCancellationToken,
33
35
  ISummarizeTelemetryProperties,
34
36
  SummaryGeneratorTelemetry,
37
+ SummaryStage,
38
+ SubmitSummaryFailureData,
35
39
  } from "./summarizerTypes";
36
40
  import { IClientSummaryWatcher } from "./summaryCollection";
37
41
 
@@ -129,7 +133,9 @@ export const getFailMessage = (errorCode: keyof typeof summarizeErrors) =>
129
133
  `${errorCode}: ${summarizeErrors[errorCode]}`;
130
134
 
131
135
  export class SummarizeResultBuilder {
132
- public readonly summarySubmitted = new Deferred<SummarizeResultPart<SubmitSummaryResult>>();
136
+ public readonly summarySubmitted = new Deferred<
137
+ SummarizeResultPart<SubmitSummaryResult, SubmitSummaryFailureData>
138
+ >();
133
139
  public readonly summaryOpBroadcasted = new Deferred<
134
140
  SummarizeResultPart<IBroadcastSummaryResult>
135
141
  >();
@@ -140,6 +146,7 @@ export class SummarizeResultBuilder {
140
146
  public fail(
141
147
  message: string,
142
148
  error: any,
149
+ stage: SummaryStage = "unknown",
143
150
  nackSummaryResult?: INackSummaryResult,
144
151
  retryAfterSeconds?: number,
145
152
  ) {
@@ -155,7 +162,7 @@ export class SummarizeResultBuilder {
155
162
  error,
156
163
  retryAfterSeconds,
157
164
  } as const;
158
- this.summarySubmitted.resolve(result);
165
+ this.summarySubmitted.resolve({ ...result, data: { stage } });
159
166
  this.summaryOpBroadcasted.resolve(result);
160
167
  this.receivedSummaryAckOrNack.resolve({ ...result, data: nackSummaryResult });
161
168
  }
@@ -168,6 +175,20 @@ export class SummarizeResultBuilder {
168
175
  }
169
176
  }
170
177
 
178
+ /**
179
+ * Errors type for errors hit during summary that may be retriable.
180
+ */
181
+ export class RetriableSummaryError extends LoggingError {
182
+ public readonly canRetry = this.retryAfterSeconds !== undefined;
183
+ constructor(
184
+ message: string,
185
+ public readonly retryAfterSeconds?: number,
186
+ props?: ITelemetryProperties,
187
+ ) {
188
+ super(message, props);
189
+ }
190
+ }
191
+
171
192
  /**
172
193
  * This class generates and tracks a summary attempt.
173
194
  */
@@ -242,6 +263,7 @@ export class SummaryGenerator {
242
263
  { start: true, end: true, cancel: "generic" },
243
264
  );
244
265
 
266
+ let summaryData: SubmitSummaryResult | undefined;
245
267
  const fail = (
246
268
  errorCode: keyof typeof summarizeErrors,
247
269
  error?: any,
@@ -270,14 +292,15 @@ export class SummaryGenerator {
270
292
  },
271
293
  error ?? reason,
272
294
  ); // disconnect & summaryAckTimeout do not have proper error.
273
- resultsBuilder.fail(reason, error, nackSummaryResult, retryAfterSeconds);
295
+
296
+ // If summarize did not hit an unexpected error, summaryData would be available. Otherwise, the state is
297
+ // unknown.
298
+ const stage = summaryData?.stage ?? "unknown";
299
+ resultsBuilder.fail(reason, error, stage, nackSummaryResult, retryAfterSeconds);
274
300
  };
275
301
 
276
302
  // Wait to generate and send summary
277
303
  this.summarizeTimer.start();
278
-
279
- // Use record type to prevent unexpected value types
280
- let summaryData: SubmitSummaryResult | undefined;
281
304
  try {
282
305
  // Need to save refSeqNum before we record new attempt (happens as part of submitSummaryCallback)
283
306
  const lastAttemptRefSeqNum = this.heuristicData.lastAttempt.refSequenceNumber;
@@ -359,10 +382,10 @@ export class SummaryGenerator {
359
382
  cancellationToken,
360
383
  );
361
384
  if (waitBroadcastResult.result === "cancelled") {
362
- return fail("disconnect");
385
+ return fail("disconnect", summaryData.stage);
363
386
  }
364
387
  if (waitBroadcastResult.result !== "done") {
365
- return fail("summaryOpWaitTimeout");
388
+ return fail("summaryOpWaitTimeout", summaryData.stage);
366
389
  }
367
390
  const summarizeOp = waitBroadcastResult.value;
368
391
 
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, IEvent, IEventProvider } from "@fluidframework/common-definitions";
6
+ import { IEvent, IEventProvider } from "@fluidframework/common-definitions";
7
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
8
  import { assert } from "@fluidframework/common-utils";
8
9
  import {
9
10
  ChildLogger,