@fluidframework/runtime-utils 2.0.0-internal.3.1.0 → 2.0.0-internal.3.2.1

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 (49) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/packageVersion.d.ts +1 -1
  5. package/dist/packageVersion.js +1 -1
  6. package/dist/packageVersion.js.map +1 -1
  7. package/dist/summarizerNode/summarizerNode.d.ts +26 -11
  8. package/dist/summarizerNode/summarizerNode.d.ts.map +1 -1
  9. package/dist/summarizerNode/summarizerNode.js +116 -54
  10. package/dist/summarizerNode/summarizerNode.js.map +1 -1
  11. package/dist/summarizerNode/summarizerNodeUtils.d.ts +12 -14
  12. package/dist/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  13. package/dist/summarizerNode/summarizerNodeUtils.js.map +1 -1
  14. package/dist/summarizerNode/summarizerNodeWithGc.d.ts +8 -114
  15. package/dist/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  16. package/dist/summarizerNode/summarizerNodeWithGc.js +30 -12
  17. package/dist/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  18. package/dist/summaryUtils.d.ts +4 -0
  19. package/dist/summaryUtils.d.ts.map +1 -1
  20. package/dist/summaryUtils.js +9 -0
  21. package/dist/summaryUtils.js.map +1 -1
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/index.js +1 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/packageVersion.d.ts +1 -1
  26. package/lib/packageVersion.js +1 -1
  27. package/lib/packageVersion.js.map +1 -1
  28. package/lib/summarizerNode/summarizerNode.d.ts +26 -11
  29. package/lib/summarizerNode/summarizerNode.d.ts.map +1 -1
  30. package/lib/summarizerNode/summarizerNode.js +116 -54
  31. package/lib/summarizerNode/summarizerNode.js.map +1 -1
  32. package/lib/summarizerNode/summarizerNodeUtils.d.ts +12 -14
  33. package/lib/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  34. package/lib/summarizerNode/summarizerNodeUtils.js.map +1 -1
  35. package/lib/summarizerNode/summarizerNodeWithGc.d.ts +8 -114
  36. package/lib/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  37. package/lib/summarizerNode/summarizerNodeWithGc.js +30 -11
  38. package/lib/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  39. package/lib/summaryUtils.d.ts +4 -0
  40. package/lib/summaryUtils.d.ts.map +1 -1
  41. package/lib/summaryUtils.js +9 -0
  42. package/lib/summaryUtils.js.map +1 -1
  43. package/package.json +110 -111
  44. package/src/index.ts +1 -0
  45. package/src/packageVersion.ts +1 -1
  46. package/src/summarizerNode/summarizerNode.ts +178 -79
  47. package/src/summarizerNode/summarizerNodeUtils.ts +12 -17
  48. package/src/summarizerNode/summarizerNodeWithGc.ts +33 -10
  49. package/src/summaryUtils.ts +14 -0
@@ -19,8 +19,14 @@ import {
19
19
  ISnapshotTree,
20
20
  SummaryObject,
21
21
  } from "@fluidframework/protocol-definitions";
22
- import { ITelemetryLogger } from "@fluidframework/common-definitions";
22
+ import { ITelemetryErrorEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
23
23
  import { assert, unreachableCase } from "@fluidframework/common-utils";
24
+ import {
25
+ ChildLogger,
26
+ LoggingError,
27
+ PerformanceEvent,
28
+ TelemetryDataTag,
29
+ } from "@fluidframework/telemetry-utils";
24
30
  import { mergeStats, convertToSummaryTree, calculateStats } from "../summaryUtils";
25
31
  import { ReadAndParseBlob } from "../utils";
26
32
  import {
@@ -35,6 +41,10 @@ import {
35
41
  SummaryNode,
36
42
  } from "./summarizerNodeUtils";
37
43
 
44
+ /**
45
+ * @deprecated Internal implementation detail and will no longer be exported in an
46
+ * upcoming release.
47
+ */
38
48
  export interface IRootSummarizerNode extends ISummarizerNode, ISummarizerNodeRootContract {}
39
49
 
40
50
  /**
@@ -61,10 +71,40 @@ export class SummarizerNode implements IRootSummarizerNode {
61
71
 
62
72
  protected readonly children = new Map<string, SummarizerNode>();
63
73
  protected readonly pendingSummaries = new Map<string, SummaryNode>();
64
- private wipReferenceSequenceNumber: number | undefined;
74
+ protected wipReferenceSequenceNumber: number | undefined;
65
75
  private wipLocalPaths: { localPath: EscapedPath; additionalPath?: EscapedPath } | undefined;
66
76
  private wipSkipRecursion = false;
67
77
 
78
+ protected readonly logger: ITelemetryLogger;
79
+
80
+ /**
81
+ * Do not call constructor directly.
82
+ * Use createRootSummarizerNode to create root node, or createChild to create child nodes.
83
+ */
84
+ public constructor(
85
+ baseLogger: ITelemetryLogger,
86
+ private readonly summarizeInternalFn: SummarizeInternalFn,
87
+ config: ISummarizerNodeConfig,
88
+ private _changeSequenceNumber: number,
89
+ /** Undefined means created without summary */
90
+ private _latestSummary?: SummaryNode,
91
+ private readonly initialSummary?: IInitialSummary,
92
+ protected wipSummaryLogger?: ITelemetryLogger,
93
+ /** A unique id of this node to be logged when sending telemetry. */
94
+ protected telemetryNodeId?: string,
95
+ ) {
96
+ this.canReuseHandle = config.canReuseHandle ?? true;
97
+ // All logs posted by the summarizer node should include the telemetryNodeId.
98
+ this.logger = ChildLogger.create(baseLogger, undefined /* namespace */, {
99
+ all: {
100
+ id: {
101
+ tag: TelemetryDataTag.CodeArtifact,
102
+ value: this.telemetryNodeId,
103
+ },
104
+ },
105
+ });
106
+ }
107
+
68
108
  public startSummary(referenceSequenceNumber: number, summaryLogger: ITelemetryLogger) {
69
109
  assert(
70
110
  this.wipSummaryLogger === undefined,
@@ -89,7 +129,7 @@ export class SummarizerNode implements IRootSummarizerNode {
89
129
  telemetryContext?: ITelemetryContext,
90
130
  ): Promise<ISummarizeResult> {
91
131
  assert(
92
- this.isTrackingInProgress(),
132
+ this.isSummaryInProgress(),
93
133
  0x1a1 /* "summarize should not be called when not tracking the summary" */,
94
134
  );
95
135
  assert(
@@ -176,10 +216,21 @@ export class SummarizerNode implements IRootSummarizerNode {
176
216
  }
177
217
  }
178
218
 
179
- // This should come from wipLocalPaths in normal cases, or from the latestSummary
180
- // if parentIsFailure or parentIsReused is true.
181
- // If there is no latestSummary, clearSummary and return before reaching this code.
182
- assert(!!localPathsToUse, 0x1a5 /* "Tracked summary local paths not set" */);
219
+ /**
220
+ * The absence of wip local path indicates that summarize was not called for this node. This can happen if:
221
+ * 1. A child node was created after summarize was already called on the parent. For example, a data store
222
+ * is realized (loaded) after summarize was called on it creating summarizer nodes for its DDSes. In this case,
223
+ * parentSkipRecursion will be true and the if block above would handle it.
224
+ * 2. A new node was created but summarize was never called on it. This can mean that the summary that is
225
+ * generated may not have the data from this node. We should not continue, log and throw an error. This
226
+ * will help us identify these cases and take appropriate action.
227
+ */
228
+ if (localPathsToUse === undefined) {
229
+ this.throwUnexpectedError({
230
+ eventName: "NodeNotSummarized",
231
+ proposalHandle,
232
+ });
233
+ }
183
234
 
184
235
  const summary = new SummaryNode({
185
236
  ...localPathsToUse,
@@ -235,66 +286,104 @@ export class SummarizerNode implements IRootSummarizerNode {
235
286
  readAndParseBlob: ReadAndParseBlob,
236
287
  correlatedSummaryLogger: ITelemetryLogger,
237
288
  ): Promise<RefreshSummaryResult> {
238
- this.defaultLogger.sendTelemetryEvent({
239
- eventName: "refreshLatestSummary_start",
289
+ const eventProps: {
290
+ proposalHandle: string | undefined;
291
+ summaryRefSeq: number;
292
+ referenceSequenceNumber: number;
293
+ latestSummaryUpdated?: boolean;
294
+ wasSummaryTracked?: boolean;
295
+ } = {
240
296
  proposalHandle,
241
- referenceSequenceNumber: this.referenceSequenceNumber,
242
297
  summaryRefSeq,
243
- });
298
+ referenceSequenceNumber: this.referenceSequenceNumber,
299
+ };
300
+ return PerformanceEvent.timedExecAsync(
301
+ this.logger,
302
+ {
303
+ eventName: "refreshLatestSummary",
304
+ ...eventProps,
305
+ },
306
+ async (event) => {
307
+ // Refresh latest summary should not happen while a summary is in progress. If it does, it can result
308
+ // in inconsistent state, so, we should not continue;
309
+ if (this.isSummaryInProgress()) {
310
+ throw new LoggingError("UnexpectedRefreshDuringSummarize", {
311
+ inProgressSummaryRefSeq: this.wipReferenceSequenceNumber,
312
+ });
313
+ }
244
314
 
245
- if (proposalHandle !== undefined) {
246
- const maybeSummaryNode = this.pendingSummaries.get(proposalHandle);
315
+ if (proposalHandle !== undefined) {
316
+ const maybeSummaryNode = this.pendingSummaries.get(proposalHandle);
247
317
 
248
- if (maybeSummaryNode !== undefined) {
249
- this.refreshLatestSummaryFromPending(
250
- proposalHandle,
251
- maybeSummaryNode.referenceSequenceNumber,
252
- );
253
- return { latestSummaryUpdated: true, wasSummaryTracked: true, summaryRefSeq };
254
- }
318
+ if (maybeSummaryNode !== undefined) {
319
+ this.refreshLatestSummaryFromPending(
320
+ proposalHandle,
321
+ maybeSummaryNode.referenceSequenceNumber,
322
+ );
323
+ eventProps.wasSummaryTracked = true;
324
+ eventProps.latestSummaryUpdated = true;
325
+ event.end(eventProps);
326
+ return {
327
+ latestSummaryUpdated: true,
328
+ wasSummaryTracked: true,
329
+ summaryRefSeq,
330
+ };
331
+ }
255
332
 
256
- const props = {
257
- summaryRefSeq,
258
- pendingSize: this.pendingSummaries.size ?? undefined,
259
- };
260
- this.defaultLogger.sendTelemetryEvent({
261
- eventName: "PendingSummaryNotFound",
262
- proposalHandle,
263
- referenceSequenceNumber: this.referenceSequenceNumber,
264
- details: JSON.stringify(props),
265
- });
266
- }
333
+ const props = {
334
+ summaryRefSeq,
335
+ pendingSize: this.pendingSummaries.size ?? undefined,
336
+ };
337
+ this.logger.sendTelemetryEvent({
338
+ eventName: "PendingSummaryNotFound",
339
+ proposalHandle,
340
+ referenceSequenceNumber: this.referenceSequenceNumber,
341
+ details: JSON.stringify(props),
342
+ });
343
+ }
267
344
 
268
- // If the summary for which refresh is called is older than the latest tracked summary, ignore it.
269
- if (this.referenceSequenceNumber >= summaryRefSeq) {
270
- return { latestSummaryUpdated: false };
271
- }
345
+ // If the summary for which refresh is called is older than the latest tracked summary, ignore it.
346
+ if (this.referenceSequenceNumber >= summaryRefSeq) {
347
+ eventProps.latestSummaryUpdated = false;
348
+ event.end(eventProps);
349
+ return { latestSummaryUpdated: false };
350
+ }
272
351
 
273
- // Fetch the latest snapshot and refresh state from it. Note that we need to use the reference sequence number
274
- // of the fetched snapshot and not the "summaryRefSeq" that was passed in.
275
- const { snapshotTree, snapshotRefSeq: fetchedSnapshotRefSeq } = await fetchLatestSnapshot();
352
+ // Fetch the latest snapshot and refresh state from it. Note that we need to use the reference sequence number
353
+ // of the fetched snapshot and not the "summaryRefSeq" that was passed in.
354
+ const { snapshotTree, snapshotRefSeq: fetchedSnapshotRefSeq } =
355
+ await fetchLatestSnapshot();
356
+
357
+ // Possible re-entrancy. We may have updated latest summary state while fetching the snapshot. If the fetched
358
+ // snapshot is older than the latest tracked summary, ignore it.
359
+ if (this.referenceSequenceNumber >= fetchedSnapshotRefSeq) {
360
+ eventProps.latestSummaryUpdated = false;
361
+ event.end(eventProps);
362
+ return { latestSummaryUpdated: false };
363
+ }
276
364
 
277
- // Possible re-entrancy. We may have updated latest summary state while fetching the snapshot. If the fetched
278
- // snapshot is older than the latest tracked summary, ignore it.
279
- if (this.referenceSequenceNumber >= fetchedSnapshotRefSeq) {
280
- return { latestSummaryUpdated: false };
281
- }
365
+ await this.refreshLatestSummaryFromSnapshot(
366
+ fetchedSnapshotRefSeq,
367
+ snapshotTree,
368
+ undefined,
369
+ EscapedPath.create(""),
370
+ correlatedSummaryLogger,
371
+ readAndParseBlob,
372
+ );
282
373
 
283
- await this.refreshLatestSummaryFromSnapshot(
284
- fetchedSnapshotRefSeq,
285
- snapshotTree,
286
- undefined,
287
- EscapedPath.create(""),
288
- correlatedSummaryLogger,
289
- readAndParseBlob,
374
+ eventProps.latestSummaryUpdated = true;
375
+ eventProps.wasSummaryTracked = false;
376
+ eventProps.summaryRefSeq = fetchedSnapshotRefSeq;
377
+ event.end(eventProps);
378
+ return {
379
+ latestSummaryUpdated: true,
380
+ wasSummaryTracked: false,
381
+ snapshotTree,
382
+ summaryRefSeq: fetchedSnapshotRefSeq,
383
+ };
384
+ },
385
+ { start: true, end: true, cancel: "error" },
290
386
  );
291
-
292
- return {
293
- latestSummaryUpdated: true,
294
- wasSummaryTracked: false,
295
- snapshotTree,
296
- summaryRefSeq: fetchedSnapshotRefSeq,
297
- };
298
387
  }
299
388
  /**
300
389
  * Called when we get an ack from the server for a summary we've just sent. Updates the reference state of this node
@@ -428,23 +517,6 @@ export class SummarizerNode implements IRootSummarizerNode {
428
517
 
429
518
  protected readonly canReuseHandle: boolean;
430
519
 
431
- /**
432
- * Do not call constructor directly.
433
- * Use createRootSummarizerNode to create root node, or createChild to create child nodes.
434
- */
435
- public constructor(
436
- protected readonly defaultLogger: ITelemetryLogger,
437
- private readonly summarizeInternalFn: SummarizeInternalFn,
438
- config: ISummarizerNodeConfig,
439
- private _changeSequenceNumber: number,
440
- /** Undefined means created without summary */
441
- private _latestSummary?: SummaryNode,
442
- private readonly initialSummary?: IInitialSummary,
443
- protected wipSummaryLogger?: ITelemetryLogger,
444
- ) {
445
- this.canReuseHandle = config.canReuseHandle ?? true;
446
- }
447
-
448
520
  public createChild(
449
521
  /** Summarize function */
450
522
  summarizeInternalFn: SummarizeInternalFn,
@@ -462,13 +534,14 @@ export class SummarizerNode implements IRootSummarizerNode {
462
534
 
463
535
  const createDetails: ICreateChildDetails = this.getCreateDetailsForChild(id, createParam);
464
536
  const child = new SummarizerNode(
465
- this.defaultLogger,
537
+ this.logger,
466
538
  summarizeInternalFn,
467
539
  config,
468
540
  createDetails.changeSequenceNumber,
469
541
  createDetails.latestSummary,
470
542
  createDetails.initialSummary,
471
543
  this.wipSummaryLogger,
544
+ createDetails.telemetryNodeId,
472
545
  );
473
546
 
474
547
  // There may be additional state that has to be updated in this child. For example, if a summary is being
@@ -574,10 +647,13 @@ export class SummarizerNode implements IRootSummarizerNode {
574
647
  }
575
648
  }
576
649
 
650
+ const childtelemetryNodeId = `${this.telemetryNodeId ?? ""}/${id}`;
651
+
577
652
  return {
578
653
  initialSummary,
579
654
  latestSummary,
580
655
  changeSequenceNumber,
656
+ telemetryNodeId: childtelemetryNodeId,
581
657
  };
582
658
  }
583
659
 
@@ -589,9 +665,9 @@ export class SummarizerNode implements IRootSummarizerNode {
589
665
  * @param child - The child node whose state is to be updated.
590
666
  */
591
667
  protected maybeUpdateChildState(child: SummarizerNode) {
592
- // If we are tracking a summary, this child was created after the tracking started. So, we need to update the
593
- // child's tracking state as well.
594
- if (this.isTrackingInProgress()) {
668
+ // If a summary is in progress, this child was created after the summary started. So, we need to update the
669
+ // child's summary state as well.
670
+ if (this.isSummaryInProgress()) {
595
671
  child.wipReferenceSequenceNumber = this.wipReferenceSequenceNumber;
596
672
  }
597
673
  // In case we have pending summaries on the parent, let's initialize it on the child.
@@ -611,12 +687,29 @@ export class SummarizerNode implements IRootSummarizerNode {
611
687
  protected addPendingSummary(key: string, summary: SummaryNode) {
612
688
  this.pendingSummaries.set(key, summary);
613
689
  }
690
+
614
691
  /**
615
692
  * Tells whether summary tracking is in progress. True if "startSummary" API is called before summarize.
616
693
  */
617
- protected isTrackingInProgress(): boolean {
694
+ public isSummaryInProgress(): boolean {
618
695
  return this.wipReferenceSequenceNumber !== undefined;
619
696
  }
697
+
698
+ /**
699
+ * Creates and throws an error due to unexpected conditions.
700
+ */
701
+ protected throwUnexpectedError(eventProps: ITelemetryErrorEvent): never {
702
+ const error = new LoggingError(eventProps.eventName, {
703
+ ...eventProps,
704
+ referenceSequenceNumber: this.wipReferenceSequenceNumber,
705
+ id: {
706
+ tag: TelemetryDataTag.CodeArtifact,
707
+ value: this.telemetryNodeId,
708
+ },
709
+ });
710
+ this.logger.sendErrorEvent(eventProps, error);
711
+ throw error;
712
+ }
620
713
  }
621
714
 
622
715
  /**
@@ -627,6 +720,9 @@ export class SummarizerNode implements IRootSummarizerNode {
627
720
  * @param referenceSequenceNumber - Reference sequence number of last acked summary,
628
721
  * or undefined if not loaded from summary
629
722
  * @param config - Configure behavior of summarizer node
723
+ *
724
+ * @deprecated Internal implementation detail and will no longer be exported in an
725
+ * upcoming release.
630
726
  */
631
727
  export const createRootSummarizerNode = (
632
728
  logger: ITelemetryLogger,
@@ -643,4 +739,7 @@ export const createRootSummarizerNode = (
643
739
  referenceSequenceNumber === undefined
644
740
  ? undefined
645
741
  : SummaryNode.createForRoot(referenceSequenceNumber),
742
+ undefined /* initialSummary */,
743
+ undefined /* wipSummaryLogger */,
744
+ "" /* telemetryNodeId */,
646
745
  );
@@ -17,6 +17,9 @@ import { ReadAndParseBlob } from "../utils";
17
17
  *
18
18
  * 3. The latest summary was updated but the summary corresponding to the params was not tracked. In this case, the
19
19
  * latest snapshot is fetched and the latest summary state is updated based on it.
20
+ *
21
+ * @deprecated Internal implementation detail and will no longer be exported in an
22
+ * upcoming release.
20
23
  */
21
24
  export type RefreshSummaryResult =
22
25
  | {
@@ -36,12 +39,19 @@ export type RefreshSummaryResult =
36
39
 
37
40
  /**
38
41
  * Result of snapshot fetch during refreshing latest summary state.
42
+ *
43
+ * @deprecated Internal implementation detail and will no longer be exported in an
44
+ * upcoming release.
39
45
  */
40
46
  export interface IFetchSnapshotResult {
41
47
  snapshotTree: ISnapshotTree;
42
48
  snapshotRefSeq: number;
43
49
  }
44
50
 
51
+ /**
52
+ * @deprecated Internal implementation detail and will no longer be exported in an
53
+ * upcoming release.
54
+ */
45
55
  export interface ISummarizerNodeRootContract {
46
56
  startSummary(referenceSequenceNumber: number, summaryLogger: ITelemetryLogger): void;
47
57
  completeSummary(proposalHandle: string): void;
@@ -143,23 +153,6 @@ export class SummaryNode {
143
153
  }
144
154
  }
145
155
 
146
- /**
147
- * Parameter to help encode summary with conditional behavior.
148
- * When fromSummary is true, it will contain the SummaryNode of
149
- * its previous summary, which it can use to point to with a handle.
150
- * When fromSummary is false, it will use an actual summary tree
151
- * as its base summary in case the first summary is a differential summary.
152
- */
153
- export type EncodeSummaryParam =
154
- | {
155
- fromSummary: true;
156
- summaryNode: SummaryNode;
157
- }
158
- | {
159
- fromSummary: false;
160
- initialSummary: ISummaryTreeWithStats;
161
- };
162
-
163
156
  /**
164
157
  * Information about the initial summary tree found from an attach op.
165
158
  */
@@ -179,6 +172,8 @@ export interface ICreateChildDetails {
179
172
  latestSummary: SummaryNode | undefined;
180
173
  /** Sequence number of latest known change to the node */
181
174
  changeSequenceNumber: number;
175
+ /** A unique id of this child to be logged when sending telemetry. */
176
+ telemetryNodeId: string;
182
177
  }
183
178
 
184
179
  export interface ISubtreeInfo<T extends ISnapshotTree | SummaryObject> {
@@ -35,6 +35,10 @@ import {
35
35
  SummaryNode,
36
36
  } from "./summarizerNodeUtils";
37
37
 
38
+ /**
39
+ * @deprecated Internal implementation detail and will no longer be exported in an
40
+ * upcoming release.
41
+ */
38
42
  export interface IRootSummarizerNodeWithGC
39
43
  extends ISummarizerNodeWithGC,
40
44
  ISummarizerNodeRootContract {}
@@ -67,7 +71,7 @@ class SummaryNodeWithGC extends SummaryNode {
67
71
  * - Adds trackState param to summarize. If trackState is false, it bypasses the SummarizerNode and calls
68
72
  * directly into summarizeInternal method.
69
73
  */
70
- export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
74
+ class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
71
75
  // Tracks the work-in-progress used routes during summary.
72
76
  private wipSerializedUsedRoutes: string | undefined;
73
77
 
@@ -109,6 +113,8 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
109
113
  wipSummaryLogger?: ITelemetryLogger,
110
114
  private readonly getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
111
115
  getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
116
+ /** A unique id of this node to be logged when sending telemetry. */
117
+ telemetryId?: string,
112
118
  ) {
113
119
  super(
114
120
  logger,
@@ -119,6 +125,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
119
125
  latestSummary,
120
126
  initialSummary,
121
127
  wipSummaryLogger,
128
+ telemetryId,
122
129
  );
123
130
 
124
131
  this.gcDisabled = config.gcDisabled === true;
@@ -164,9 +171,9 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
164
171
  trackState: boolean = true,
165
172
  telemetryContext?: ITelemetryContext,
166
173
  ): Promise<ISummarizeResult> {
167
- // If GC is not disabled and we are tracking a summary, GC should have run and updated the used routes for this
174
+ // If GC is not disabled and a summary is in progress, GC should have run and updated the used routes for this
168
175
  // summary by calling updateUsedRoutes which sets wipSerializedUsedRoutes.
169
- if (!this.gcDisabled && this.isTrackingInProgress()) {
176
+ if (!this.gcDisabled && this.isSummaryInProgress()) {
170
177
  assert(
171
178
  this.wipSerializedUsedRoutes !== undefined,
172
179
  0x1b1 /* "wip used routes should be set if tracking a summary" */,
@@ -239,10 +246,21 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
239
246
  // If GC is disabled, don't set wip used routes.
240
247
  if (!this.gcDisabled) {
241
248
  wipSerializedUsedRoutes = this.wipSerializedUsedRoutes;
242
- assert(
243
- wipSerializedUsedRoutes !== undefined,
244
- 0x1b5 /* "We should have been tracking used routes" */,
245
- );
249
+ /**
250
+ * The absence of wip used routes indicates that GC was not run on this node. This can happen if:
251
+ * 1. A child node was created after GC was already run on the parent. For example, a data store
252
+ * is realized (loaded) after GC was run on it creating summarizer nodes for its DDSes. In this
253
+ * case, the used routes of the parent should be passed on the child nodes and it should be fine.
254
+ * 2. A new node was created but GC was never run on it. This can mean that the GC data generated
255
+ * during summarize is complete . We should not continue, log and throw an error. This will help us
256
+ * identify these cases and take appropriate action.
257
+ */
258
+ if (wipSerializedUsedRoutes === undefined) {
259
+ this.throwUnexpectedError({
260
+ eventName: "NodeDidNotRunGC",
261
+ proposalHandle,
262
+ });
263
+ }
246
264
  }
247
265
 
248
266
  super.completeSummaryCore(proposalHandle, parentPath, parentSkipRecursion);
@@ -436,7 +454,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
436
454
 
437
455
  const createDetails: ICreateChildDetails = this.getCreateDetailsForChild(id, createParam);
438
456
  const child = new SummarizerNodeWithGC(
439
- this.defaultLogger,
457
+ this.logger,
440
458
  summarizeInternalFn,
441
459
  {
442
460
  ...config,
@@ -449,6 +467,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
449
467
  this.wipSummaryLogger,
450
468
  getGCDataFn,
451
469
  async () => getChildBaseGCDetailsP,
470
+ createDetails.telemetryNodeId,
452
471
  );
453
472
 
454
473
  // There may be additional state that has to be updated in this child. For example, if a summary is being
@@ -482,9 +501,9 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
482
501
  // are in the same order.
483
502
  this.usedRoutes = usedRoutes.sort();
484
503
 
485
- // If GC is not disabled and we are tracking a summary, update the work-in-progress used routes so that it can
504
+ // If GC is not disabled and a summary is in progress, update the work-in-progress used routes so that it can
486
505
  // be tracked for this summary.
487
- if (!this.gcDisabled && this.isTrackingInProgress()) {
506
+ if (!this.gcDisabled && this.isSummaryInProgress()) {
488
507
  this.wipSerializedUsedRoutes = JSON.stringify(this.usedRoutes);
489
508
  }
490
509
  }
@@ -531,6 +550,9 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
531
550
  * @param config - Configure behavior of summarizer node
532
551
  * @param getGCDataFn - Function to get the GC data of this node
533
552
  * @param baseGCDetailsP - Function to get the initial GC details of this node
553
+ *
554
+ * @deprecated Internal implementation detail and will no longer be exported in an
555
+ * upcoming release.
534
556
  */
535
557
  export const createRootSummarizerNodeWithGC = (
536
558
  logger: ITelemetryLogger,
@@ -553,4 +575,5 @@ export const createRootSummarizerNodeWithGC = (
553
575
  undefined /* wipSummaryLogger */,
554
576
  getGCDataFn,
555
577
  getBaseGCDetailsFn,
578
+ "" /* telemetryId */,
556
579
  );
@@ -356,6 +356,20 @@ export class TelemetryContext implements ITelemetryContext {
356
356
  this.telemetry.set(`${prefix}${property}`, value);
357
357
  }
358
358
 
359
+ /**
360
+ * {@inheritDoc @fluidframework/runtime-definitions#ITelemetryContext.setMultiple}
361
+ */
362
+ setMultiple(
363
+ prefix: string,
364
+ property: string,
365
+ values: Record<string, TelemetryEventPropertyType>,
366
+ ): void {
367
+ // Set the values individually so that they are logged as a flat list along with other properties.
368
+ for (const key of Object.keys(values)) {
369
+ this.set(prefix, `${property}_${key}`, values[key]);
370
+ }
371
+ }
372
+
359
373
  /**
360
374
  * {@inheritDoc @fluidframework/runtime-definitions#ITelemetryContext.get}
361
375
  */