@fluidframework/runtime-utils 2.0.0-internal.3.0.5 → 2.0.0-internal.3.1.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 (90) hide show
  1. package/.eslintrc.js +5 -7
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/dataStoreHandleContextUtils.d.ts.map +1 -1
  5. package/dist/dataStoreHandleContextUtils.js +3 -1
  6. package/dist/dataStoreHandleContextUtils.js.map +1 -1
  7. package/dist/dataStoreHelpers.d.ts.map +1 -1
  8. package/dist/dataStoreHelpers.js +23 -7
  9. package/dist/dataStoreHelpers.js.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/objectstoragepartition.d.ts.map +1 -1
  13. package/dist/objectstoragepartition.js.map +1 -1
  14. package/dist/objectstorageutils.d.ts.map +1 -1
  15. package/dist/objectstorageutils.js.map +1 -1
  16. package/dist/packageVersion.d.ts +1 -1
  17. package/dist/packageVersion.js +1 -1
  18. package/dist/packageVersion.js.map +1 -1
  19. package/dist/requestParser.d.ts.map +1 -1
  20. package/dist/requestParser.js.map +1 -1
  21. package/dist/runtimeFactoryHelper.d.ts.map +1 -1
  22. package/dist/runtimeFactoryHelper.js +6 -2
  23. package/dist/runtimeFactoryHelper.js.map +1 -1
  24. package/dist/summarizerNode/summarizerNode.d.ts +1 -1
  25. package/dist/summarizerNode/summarizerNode.d.ts.map +1 -1
  26. package/dist/summarizerNode/summarizerNode.js +12 -9
  27. package/dist/summarizerNode/summarizerNode.js.map +1 -1
  28. package/dist/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  29. package/dist/summarizerNode/summarizerNodeUtils.js.map +1 -1
  30. package/dist/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  31. package/dist/summarizerNode/summarizerNodeWithGc.js +24 -16
  32. package/dist/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  33. package/dist/summaryUtils.d.ts +0 -4
  34. package/dist/summaryUtils.d.ts.map +1 -1
  35. package/dist/summaryUtils.js +3 -11
  36. package/dist/summaryUtils.js.map +1 -1
  37. package/dist/utils.d.ts.map +1 -1
  38. package/dist/utils.js.map +1 -1
  39. package/lib/dataStoreHandleContextUtils.d.ts.map +1 -1
  40. package/lib/dataStoreHandleContextUtils.js +3 -1
  41. package/lib/dataStoreHandleContextUtils.js.map +1 -1
  42. package/lib/dataStoreHelpers.d.ts.map +1 -1
  43. package/lib/dataStoreHelpers.js +23 -7
  44. package/lib/dataStoreHelpers.js.map +1 -1
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js.map +1 -1
  47. package/lib/objectstoragepartition.d.ts.map +1 -1
  48. package/lib/objectstoragepartition.js.map +1 -1
  49. package/lib/objectstorageutils.d.ts.map +1 -1
  50. package/lib/objectstorageutils.js.map +1 -1
  51. package/lib/packageVersion.d.ts +1 -1
  52. package/lib/packageVersion.js +1 -1
  53. package/lib/packageVersion.js.map +1 -1
  54. package/lib/requestParser.d.ts.map +1 -1
  55. package/lib/requestParser.js.map +1 -1
  56. package/lib/runtimeFactoryHelper.d.ts.map +1 -1
  57. package/lib/runtimeFactoryHelper.js +6 -2
  58. package/lib/runtimeFactoryHelper.js.map +1 -1
  59. package/lib/summarizerNode/summarizerNode.d.ts +1 -1
  60. package/lib/summarizerNode/summarizerNode.d.ts.map +1 -1
  61. package/lib/summarizerNode/summarizerNode.js +12 -9
  62. package/lib/summarizerNode/summarizerNode.js.map +1 -1
  63. package/lib/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  64. package/lib/summarizerNode/summarizerNodeUtils.js.map +1 -1
  65. package/lib/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  66. package/lib/summarizerNode/summarizerNodeWithGc.js +25 -17
  67. package/lib/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  68. package/lib/summaryUtils.d.ts +0 -4
  69. package/lib/summaryUtils.d.ts.map +1 -1
  70. package/lib/summaryUtils.js +3 -11
  71. package/lib/summaryUtils.js.map +1 -1
  72. package/lib/utils.d.ts.map +1 -1
  73. package/lib/utils.js.map +1 -1
  74. package/package.json +110 -112
  75. package/prettier.config.cjs +1 -1
  76. package/src/dataStoreHandleContextUtils.ts +23 -16
  77. package/src/dataStoreHelpers.ts +104 -86
  78. package/src/index.ts +2 -2
  79. package/src/objectstoragepartition.ts +13 -13
  80. package/src/objectstorageutils.ts +31 -28
  81. package/src/packageVersion.ts +1 -1
  82. package/src/requestParser.ts +80 -82
  83. package/src/runtimeFactoryHelper.ts +24 -17
  84. package/src/summarizerNode/summarizerNode.ts +602 -574
  85. package/src/summarizerNode/summarizerNodeUtils.ts +154 -151
  86. package/src/summarizerNode/summarizerNodeWithGc.ts +508 -472
  87. package/src/summaryUtils.ts +299 -305
  88. package/src/utils.ts +6 -6
  89. package/tsconfig.esnext.json +6 -6
  90. package/tsconfig.json +8 -12
@@ -5,46 +5,53 @@
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { assert, LazyPromise } from "@fluidframework/common-utils";
8
- import { cloneGCData, getGCDataFromSnapshot, runGarbageCollection, unpackChildNodesGCDetails } from "@fluidframework/garbage-collector";
8
+ import {
9
+ cloneGCData,
10
+ getGCDataFromSnapshot,
11
+ runGarbageCollection,
12
+ unpackChildNodesGCDetails,
13
+ } from "@fluidframework/garbage-collector";
9
14
  import { ISnapshotTree } from "@fluidframework/protocol-definitions";
10
15
  import {
11
- CreateChildSummarizerNodeParam,
12
- gcTreeKey,
13
- IGarbageCollectionData,
14
- IGarbageCollectionDetailsBase,
15
- ISummarizeInternalResult,
16
- ISummarizeResult,
17
- ISummarizerNodeConfigWithGC,
18
- ISummarizerNodeWithGC,
19
- SummarizeInternalFn,
20
- ITelemetryContext,
16
+ CreateChildSummarizerNodeParam,
17
+ gcTreeKey,
18
+ IGarbageCollectionData,
19
+ IGarbageCollectionDetailsBase,
20
+ ISummarizeInternalResult,
21
+ ISummarizeResult,
22
+ ISummarizerNodeConfigWithGC,
23
+ ISummarizerNodeWithGC,
24
+ SummarizeInternalFn,
25
+ ITelemetryContext,
21
26
  } from "@fluidframework/runtime-definitions";
22
27
  import { ReadAndParseBlob } from "../utils";
23
28
  import { SummarizerNode } from "./summarizerNode";
24
29
  import {
25
- EscapedPath,
26
- ICreateChildDetails,
27
- IInitialSummary,
28
- ISummarizerNodeRootContract,
29
- parseSummaryForSubtrees,
30
- SummaryNode,
30
+ EscapedPath,
31
+ ICreateChildDetails,
32
+ IInitialSummary,
33
+ ISummarizerNodeRootContract,
34
+ parseSummaryForSubtrees,
35
+ SummaryNode,
31
36
  } from "./summarizerNodeUtils";
32
37
 
33
- export interface IRootSummarizerNodeWithGC extends ISummarizerNodeWithGC, ISummarizerNodeRootContract {}
38
+ export interface IRootSummarizerNodeWithGC
39
+ extends ISummarizerNodeWithGC,
40
+ ISummarizerNodeRootContract {}
34
41
 
35
42
  // Extend SummaryNode to add used routes tracking to it.
36
43
  class SummaryNodeWithGC extends SummaryNode {
37
- constructor(
38
- public readonly serializedUsedRoutes: string,
39
- summary: {
40
- readonly referenceSequenceNumber: number;
41
- readonly basePath: EscapedPath | undefined;
42
- readonly localPath: EscapedPath;
43
- additionalPath?: EscapedPath;
44
- },
45
- ) {
46
- super(summary);
47
- }
44
+ constructor(
45
+ public readonly serializedUsedRoutes: string,
46
+ summary: {
47
+ readonly referenceSequenceNumber: number;
48
+ readonly basePath: EscapedPath | undefined;
49
+ readonly localPath: EscapedPath;
50
+ additionalPath?: EscapedPath;
51
+ },
52
+ ) {
53
+ super(summary);
54
+ }
48
55
  }
49
56
 
50
57
  /**
@@ -61,431 +68,457 @@ class SummaryNodeWithGC extends SummaryNode {
61
68
  * directly into summarizeInternal method.
62
69
  */
63
70
  export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
64
- // Tracks the work-in-progress used routes during summary.
65
- private wipSerializedUsedRoutes: string | undefined;
66
-
67
- // This is the last known used routes of this node as seen by the server as part of a summary.
68
- private referenceUsedRoutes: string[] | undefined;
69
-
70
- // The base GC details of this node used to initialize the GC state.
71
- private readonly baseGCDetailsP: LazyPromise<IGarbageCollectionDetailsBase>;
72
-
73
- // Keeps track of whether we have loaded the base details to ensure that we on;y do it once.
74
- private baseGCDetailsLoaded: boolean = false;
75
-
76
- private gcData: IGarbageCollectionData | undefined;
77
-
78
- // Set used routes to have self route by default. This makes the node referenced by default. This is done to ensure
79
- // that this node is not marked as collected when running GC has been disabled. Once, the option to disable GC is
80
- // removed (from runGC flag in IContainerRuntimeOptions), this should be changed to be have no routes by default.
81
- private usedRoutes: string[] = [""];
82
-
83
- // True if GC is disabled for this node. If so, do not track GC specific state for a summary.
84
- private readonly gcDisabled: boolean;
85
-
86
- /**
87
- * Do not call constructor directly.
88
- * Use createRootSummarizerNodeWithGC to create root node, or createChild to create child nodes.
89
- */
90
- public constructor(
91
- logger: ITelemetryLogger,
92
- private readonly summarizeFn: (
93
- fullTree: boolean,
94
- trackState: boolean,
95
- telemetryContext?: ITelemetryContext,
96
- ) => Promise<ISummarizeInternalResult>,
97
- config: ISummarizerNodeConfigWithGC,
98
- changeSequenceNumber: number,
99
- /** Undefined means created without summary */
100
- latestSummary?: SummaryNode,
101
- initialSummary?: IInitialSummary,
102
- wipSummaryLogger?: ITelemetryLogger,
103
- private readonly getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
104
- getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
105
- ) {
106
- super(
107
- logger,
108
- async (fullTree: boolean, _trackState: boolean, telemetryContext?: ITelemetryContext) =>
109
- summarizeFn(fullTree, true /* trackState */, telemetryContext),
110
- config,
111
- changeSequenceNumber,
112
- latestSummary,
113
- initialSummary,
114
- wipSummaryLogger,
115
- );
116
-
117
- this.gcDisabled = config.gcDisabled === true;
118
-
119
- this.baseGCDetailsP = new LazyPromise(async () => {
120
- return (await getBaseGCDetailsFn?.()) ?? { usedRoutes: [] };
121
- });
122
- }
123
-
124
- /**
125
- * Loads state from this node's initial GC summary details. This contains the following data from the last summary
126
- * seen by the server for this client:
127
- * - usedRoutes: This is used to figure out if the used state of this node changed since last summary.
128
- * - gcData: The garbage collection data of this node that is required for running GC.
129
- */
130
- private async loadBaseGCDetails() {
131
- if (this.baseGCDetailsLoaded) {
132
- return;
133
- }
134
- const baseGCDetails = await this.baseGCDetailsP;
135
-
136
- // Possible race - If there were parallel calls to loadBaseGCDetails, we want to make sure that we update
137
- // the state from the base details only once.
138
- if (this.baseGCDetailsLoaded) {
139
- return;
140
- }
141
- this.baseGCDetailsLoaded = true;
142
-
143
- // Update GC data, used routes and reference used routes. The used routes are sorted because they are compared
144
- // across GC runs to check if they changed. Sorting ensures that the elements are in the same order.
145
- // If the GC details has GC data, initialize our GC data from it.
146
- if (baseGCDetails.gcData !== undefined) {
147
- this.gcData = cloneGCData(baseGCDetails.gcData);
148
- }
149
- if (baseGCDetails.usedRoutes !== undefined) {
150
- this.usedRoutes = Array.from(baseGCDetails.usedRoutes).sort();
151
- this.referenceUsedRoutes = Array.from(baseGCDetails.usedRoutes).sort()
152
- }
153
- }
154
-
155
- public async summarize(
156
- fullTree: boolean,
157
- trackState: boolean = true,
158
- telemetryContext?: ITelemetryContext,
159
- ): Promise<ISummarizeResult> {
160
- // If GC is not disabled and a summary is in progress, GC should have run and updated the used routes for this
161
- // summary by calling updateUsedRoutes which sets wipSerializedUsedRoutes.
162
- if (!this.gcDisabled && this.isSummaryInProgress()) {
163
- assert(this.wipSerializedUsedRoutes !== undefined,
164
- 0x1b1 /* "wip used routes should be set if tracking a summary" */);
165
- }
166
-
167
- // If trackState is true, get summary from base summarizer node which tracks summary state.
168
- // If trackState is false, get summary from summarizeInternal.
169
- return trackState
170
- ? super.summarize(fullTree, true /* trackState */, telemetryContext)
171
- : this.summarizeFn(fullTree, trackState, telemetryContext);
172
- }
173
-
174
- /**
175
- * Returns the GC data of this node. If nothing has changed since last summary, it tries to reuse the data from
176
- * the previous summary. Else, it gets new GC data from the underlying Fluid object.
177
- * @param fullGC - true to bypass optimizations and force full generation of GC data.
178
- */
179
- public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
180
- assert(!this.gcDisabled, 0x1b2 /* "Getting GC data should not be called when GC is disabled!" */);
181
- assert(this.getGCDataFn !== undefined, 0x1b3 /* "GC data cannot be retrieved without getGCDataFn" */);
182
-
183
- // Load GC details from the initial summary, if not already loaded. If this is the first time this function is
184
- // called and the node's data has not changed since last summary, the GC data in initial details is returned.
185
- await this.loadBaseGCDetails();
186
-
187
- // If there is no new data since last summary and we have GC data from the previous run, return it. The previous
188
- // GC data may not be available if loaded from a snapshot with either GC disabled or before GC was added.
189
- // Note - canReuseHandle is checked to be consistent with summarize - generate GC data for nodes for which
190
- // summary must be generated.
191
- if (this.canReuseHandle && !fullGC && !this.hasDataChanged() && this.gcData !== undefined) {
192
- return cloneGCData(this.gcData);
193
- }
194
-
195
- const gcData = await this.getGCDataFn(fullGC);
196
- this.gcData = cloneGCData(gcData);
197
- return gcData;
198
- }
199
-
200
- /**
201
- * Called during the start of a summary. Updates the work-in-progress used routes.
202
- */
203
- public startSummary(referenceSequenceNumber: number, summaryLogger: ITelemetryLogger) {
204
- // If GC is disabled, skip setting wip used routes since we should not track GC state.
205
- if (!this.gcDisabled) {
206
- assert(
207
- this.wipSerializedUsedRoutes === undefined,
208
- 0x1b4 /* "We should not already be tracking used routes when to track a new summary" */);
209
- }
210
- super.startSummary(referenceSequenceNumber, summaryLogger);
211
- }
212
-
213
- /**
214
- * Called after summary has been uploaded to the server. Add the work-in-progress state to the pending
215
- * summary queue. We track this until we get an ack from the server for this summary.
216
- */
217
- protected completeSummaryCore(
218
- proposalHandle: string,
219
- parentPath: EscapedPath | undefined,
220
- parentSkipRecursion: boolean,
221
- ) {
222
- let wipSerializedUsedRoutes: string | undefined;
223
- // If GC is disabled, don't set wip used routes.
224
- if (!this.gcDisabled) {
225
- wipSerializedUsedRoutes = this.wipSerializedUsedRoutes;
226
- assert(wipSerializedUsedRoutes !== undefined, 0x1b5 /* "We should have been tracking used routes" */);
227
- }
228
-
229
- super.completeSummaryCore(proposalHandle, parentPath, parentSkipRecursion);
230
-
231
- // If GC is disabled, skip setting pending summary with GC state.
232
- if (!this.gcDisabled) {
233
- const summaryNode = this.pendingSummaries.get(proposalHandle);
234
- if (summaryNode !== undefined) {
235
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
236
- const summaryNodeWithGC = new SummaryNodeWithGC(wipSerializedUsedRoutes!, summaryNode);
237
- this.pendingSummaries.set(proposalHandle, summaryNodeWithGC);
238
- }
239
- }
240
- }
241
-
242
- /**
243
- * Clears the work-in-progress state.
244
- */
245
- public clearSummary() {
246
- this.wipSerializedUsedRoutes = undefined;
247
- super.clearSummary();
248
- }
249
-
250
- /**
251
- * Called when we get an ack from the server for a summary we sent. Update the reference state of this node
252
- * from the state in the pending summary queue.
253
- */
254
- protected refreshLatestSummaryFromPending(
255
- proposalHandle: string,
256
- referenceSequenceNumber: number,
257
- ): void {
258
- // If GC is disabled, skip setting referenced used routes since we are not tracking GC state.
259
- if (!this.gcDisabled) {
260
- const summaryNode = this.pendingSummaries.get(proposalHandle) as SummaryNodeWithGC;
261
- if (summaryNode?.serializedUsedRoutes !== undefined) {
262
- this.referenceUsedRoutes = JSON.parse(summaryNode.serializedUsedRoutes);
263
- }
264
- }
265
-
266
- return super.refreshLatestSummaryFromPending(proposalHandle, referenceSequenceNumber);
267
- }
268
-
269
- /**
270
- * Called when we need to upload the reference state from the given summary.
271
- */
272
- protected async refreshLatestSummaryFromSnapshot(
273
- referenceSequenceNumber: number,
274
- snapshotTree: ISnapshotTree,
275
- basePath: EscapedPath | undefined,
276
- localPath: EscapedPath,
277
- correlatedSummaryLogger: ITelemetryLogger,
278
- readAndParseBlob: ReadAndParseBlob,
279
- ): Promise<void> {
280
- await this.refreshGCStateFromSnapshot(referenceSequenceNumber, snapshotTree, readAndParseBlob);
281
- return super.refreshLatestSummaryFromSnapshot(
282
- referenceSequenceNumber,
283
- snapshotTree,
284
- basePath,
285
- localPath,
286
- correlatedSummaryLogger,
287
- readAndParseBlob,
288
- );
289
- }
290
-
291
- /**
292
- * Updates GC state from the given snapshot if GC is enabled and the snapshot is newer than the one this node
293
- * is tracking.
294
- */
295
- private async refreshGCStateFromSnapshot(
296
- referenceSequenceNumber: number,
297
- snapshotTree: ISnapshotTree,
298
- readAndParseBlob: ReadAndParseBlob,
299
- ): Promise<void> {
300
- // If GC is disabled or we have seen a newer summary, skip updating GC state.
301
- if (this.gcDisabled || this.referenceSequenceNumber >= referenceSequenceNumber) {
302
- return;
303
- }
304
-
305
- // Load the base GC details before proceeding because if that happens later it can overwrite the GC details
306
- // written by the following code.
307
- await this.loadBaseGCDetails();
308
-
309
- // Possible re-entrancy. We may already have processed this while loading base GC details.
310
- if (this.referenceSequenceNumber >= referenceSequenceNumber) {
311
- return;
312
- }
313
-
314
- /**
315
- * GC data is written at root of the snapshot tree under "gc" sub-tree. This data needs to be propagated to
316
- * all the nodes in the container.
317
- * The root summarizer node reads the GC data from the "gc" sub-tree, runs GC on it to get used routes in
318
- * the container and updates its GC data and referenced used routes. It then gets the GC data and used
319
- * routes of all its children and adds it to their snapshot tree.
320
- * All the other nodes gets the GC data and used routes from their snapshot tree and updates their state.
321
- * They get the GC data and used routes of their children and add it to their snapshot tree and so on.
322
- *
323
- * Note that if the snapshot does not have GC tree, GC data will be set to undefined and used routes will be
324
- * set to self-route (meaning referenced) for all nodes. This is important because the GC data needs to be
325
- * regenerated in the next summary.
326
- */
327
- let gcDetails: IGarbageCollectionDetailsBase | undefined;
328
- const gcSnapshotTree = snapshotTree.trees[gcTreeKey];
329
- if (gcSnapshotTree !== undefined) {
330
- // If there is a GC tree in the snapshot, this is the root summarizer node. Read GC data from the tree
331
- // process it as explained above.
332
- const gcSnapshotData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
333
-
334
- const gcNodes: { [id: string]: string[]; } = {};
335
- for (const [nodeId, nodeData] of Object.entries(gcSnapshotData.gcState.gcNodes)) {
336
- gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
337
- }
338
- // Run GC on the nodes in the snapshot to get the used routes for each node in the container.
339
- const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
340
- gcDetails = { gcData: { gcNodes }, usedRoutes };
341
- } else {
342
- // If there is a GC blob in the snapshot, it's a non-root summarizer nodes - The root summarizer node
343
- // writes GC blob in the snapshot of child nodes. Get GC data and used routes from the blob.
344
- const gcDetailsBlob = snapshotTree.blobs[gcTreeKey];
345
- if (gcDetailsBlob !== undefined) {
346
- gcDetails = JSON.parse(gcDetailsBlob) as IGarbageCollectionDetailsBase;
347
- }
348
- }
349
-
350
- // Update this node to the same GC state it was when the ack corresponding to this summary was processed.
351
- this.gcData = gcDetails?.gcData !== undefined ? cloneGCData(gcDetails.gcData) : undefined;
352
- this.referenceUsedRoutes = gcDetails?.usedRoutes !== undefined ? Array.from(gcDetails.usedRoutes) : undefined;
353
- // If there are no used routes in the GC details, set it to have self route which will make the node
354
- // referenced. This scenario can only happen if the snapshot is from a client where GC was not run or
355
- // disabled. In both the cases, the node should be referenced.
356
- this.usedRoutes = gcDetails?.usedRoutes !== undefined ? Array.from(gcDetails.usedRoutes) : [""];
357
-
358
- if (gcDetails === undefined) {
359
- return;
360
- }
361
-
362
- // Generate the GC data and used routes of children GC nodes and add it to their snapshot tree.
363
- const gcDetailsMap = unpackChildNodesGCDetails(gcDetails);
364
- const { childrenTree } = parseSummaryForSubtrees(snapshotTree);
365
- gcDetailsMap.forEach((childGCDetails: IGarbageCollectionDetailsBase, childId: string) => {
366
- if (childrenTree.trees[childId] !== undefined) {
367
- childrenTree.trees[childId].blobs[gcTreeKey] = JSON.stringify(childGCDetails);
368
- }
369
- });
370
- }
371
-
372
- /**
373
- * Override the createChild method to return an instance of SummarizerNodeWithGC.
374
- */
375
- public createChild(
376
- /** Summarize function */
377
- summarizeInternalFn: SummarizeInternalFn,
378
- /** Initial id or path part of this node */
379
- id: string,
380
- /**
381
- * Information needed to create the node.
382
- * If it is from a base summary, it will assert that a summary has been seen.
383
- * Attach information if it is created from an attach op.
384
- */
385
- createParam: CreateChildSummarizerNodeParam,
386
- config: ISummarizerNodeConfigWithGC = {},
387
- getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
388
- getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
389
- ): ISummarizerNodeWithGC {
390
- assert(!this.children.has(id), 0x1b6 /* "Create SummarizerNode child already exists" */);
391
-
392
- /**
393
- * Update the child node's base GC details from this node's current GC details instead of updating from the base
394
- * GC details of this node. This will handle scenarios where the GC details was updated during refresh from
395
- * snapshot and the child node wasn't created then. If a child is created after that, its GC details should be
396
- * the one from the downloaded snapshot and not the base GC details.
397
- */
398
- const getChildBaseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
399
- // Ensure that the base GC details is loaded because a child can be created before GC runs which is when
400
- // base GC details is usually loaded.
401
- await this.loadBaseGCDetails();
402
- const childBaseGCDetails = unpackChildNodesGCDetails({ gcData: this.gcData, usedRoutes: this.usedRoutes });
403
- return childBaseGCDetails.get(id) ?? {};
404
- });
405
-
406
- const createDetails: ICreateChildDetails = this.getCreateDetailsForChild(id, createParam);
407
- const child = new SummarizerNodeWithGC(
408
- this.defaultLogger,
409
- summarizeInternalFn,
410
- {
411
- ...config,
412
- // Propagate our gcDisabled state to the child if its not explicity specified in child's config.
413
- gcDisabled: config.gcDisabled ?? this.gcDisabled,
414
- },
415
- createDetails.changeSequenceNumber,
416
- createDetails.latestSummary,
417
- createDetails.initialSummary,
418
- this.wipSummaryLogger,
419
- getGCDataFn,
420
- async () => getChildBaseGCDetailsP,
421
- );
422
-
423
- // There may be additional state that has to be updated in this child. For example, if a summary is being
424
- // tracked, the child's summary tracking state needs to be updated too.
425
- this.maybeUpdateChildState(child);
426
-
427
- this.children.set(id, child);
428
- return child;
429
- }
430
-
431
- /**
432
- * Deletes the child node with the given id.
433
- */
434
- public deleteChild(id: string): void {
435
- this.children.delete(id);
436
- }
437
-
438
- /**
439
- * Override the getChild method to return an instance of SummarizerNodeWithGC.
440
- */
441
- public getChild(id: string): ISummarizerNodeWithGC | undefined {
442
- return this.children.get(id) as SummarizerNodeWithGC;
443
- }
444
-
445
- public isReferenced(): boolean {
446
- return this.usedRoutes.includes("") || this.usedRoutes.includes("/");
447
- }
448
-
449
- public updateUsedRoutes(usedRoutes: string[]) {
450
- // Sort the given routes before updating. This will ensure that the routes compared in hasUsedStateChanged()
451
- // are in the same order.
452
- this.usedRoutes = usedRoutes.sort();
453
-
454
- // If GC is not disabled and a summary is in progress, update the work-in-progress used routes so that it can
455
- // be tracked for this summary.
456
- if (!this.gcDisabled && this.isSummaryInProgress()) {
457
- this.wipSerializedUsedRoutes = JSON.stringify(this.usedRoutes);
458
- }
459
- }
460
-
461
- /**
462
- * Override the hasChanged method. If this node data or its used state changed, the node is considered changed.
463
- */
464
- protected hasChanged(): boolean {
465
- return this.hasDataChanged() || this.hasUsedStateChanged();
466
- }
467
-
468
- /**
469
- * This tells whether the data in this node has changed or not.
470
- */
471
- private hasDataChanged(): boolean {
472
- return super.hasChanged();
473
- }
474
-
475
- /**
476
- * This tells whether the used state of this node has changed since last successful summary. If the used routes
477
- * of this node changed, its used state is considered changed. Basically, if this node or any of its child nodes
478
- * was previously used and became unused (or vice versa), its used state has changed.
479
- */
480
- private hasUsedStateChanged(): boolean {
481
- // If GC is disabled, we are not tracking used state, return false.
482
- if (this.gcDisabled) {
483
- return false;
484
- }
485
-
486
- return this.referenceUsedRoutes === undefined ||
487
- JSON.stringify(this.usedRoutes) !== JSON.stringify(this.referenceUsedRoutes);
488
- }
71
+ // Tracks the work-in-progress used routes during summary.
72
+ private wipSerializedUsedRoutes: string | undefined;
73
+
74
+ // This is the last known used routes of this node as seen by the server as part of a summary.
75
+ private referenceUsedRoutes: string[] | undefined;
76
+
77
+ // The base GC details of this node used to initialize the GC state.
78
+ private readonly baseGCDetailsP: LazyPromise<IGarbageCollectionDetailsBase>;
79
+
80
+ // Keeps track of whether we have loaded the base details to ensure that we on;y do it once.
81
+ private baseGCDetailsLoaded: boolean = false;
82
+
83
+ private gcData: IGarbageCollectionData | undefined;
84
+
85
+ // Set used routes to have self route by default. This makes the node referenced by default. This is done to ensure
86
+ // that this node is not marked as collected when running GC has been disabled. Once, the option to disable GC is
87
+ // removed (from runGC flag in IContainerRuntimeOptions), this should be changed to be have no routes by default.
88
+ private usedRoutes: string[] = [""];
89
+
90
+ // True if GC is disabled for this node. If so, do not track GC specific state for a summary.
91
+ private readonly gcDisabled: boolean;
92
+
93
+ /**
94
+ * Do not call constructor directly.
95
+ * Use createRootSummarizerNodeWithGC to create root node, or createChild to create child nodes.
96
+ */
97
+ public constructor(
98
+ logger: ITelemetryLogger,
99
+ private readonly summarizeFn: (
100
+ fullTree: boolean,
101
+ trackState: boolean,
102
+ telemetryContext?: ITelemetryContext,
103
+ ) => Promise<ISummarizeInternalResult>,
104
+ config: ISummarizerNodeConfigWithGC,
105
+ changeSequenceNumber: number,
106
+ /** Undefined means created without summary */
107
+ latestSummary?: SummaryNode,
108
+ initialSummary?: IInitialSummary,
109
+ wipSummaryLogger?: ITelemetryLogger,
110
+ private readonly getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
111
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
112
+ ) {
113
+ super(
114
+ logger,
115
+ async (fullTree: boolean, _trackState: boolean, telemetryContext?: ITelemetryContext) =>
116
+ summarizeFn(fullTree, true /* trackState */, telemetryContext),
117
+ config,
118
+ changeSequenceNumber,
119
+ latestSummary,
120
+ initialSummary,
121
+ wipSummaryLogger,
122
+ );
123
+
124
+ this.gcDisabled = config.gcDisabled === true;
125
+
126
+ this.baseGCDetailsP = new LazyPromise(async () => {
127
+ return (await getBaseGCDetailsFn?.()) ?? { usedRoutes: [] };
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Loads state from this node's initial GC summary details. This contains the following data from the last summary
133
+ * seen by the server for this client:
134
+ * - usedRoutes: This is used to figure out if the used state of this node changed since last summary.
135
+ * - gcData: The garbage collection data of this node that is required for running GC.
136
+ */
137
+ private async loadBaseGCDetails() {
138
+ if (this.baseGCDetailsLoaded) {
139
+ return;
140
+ }
141
+ const baseGCDetails = await this.baseGCDetailsP;
142
+
143
+ // Possible race - If there were parallel calls to loadBaseGCDetails, we want to make sure that we update
144
+ // the state from the base details only once.
145
+ if (this.baseGCDetailsLoaded) {
146
+ return;
147
+ }
148
+ this.baseGCDetailsLoaded = true;
149
+
150
+ // Update GC data, used routes and reference used routes. The used routes are sorted because they are compared
151
+ // across GC runs to check if they changed. Sorting ensures that the elements are in the same order.
152
+ // If the GC details has GC data, initialize our GC data from it.
153
+ if (baseGCDetails.gcData !== undefined) {
154
+ this.gcData = cloneGCData(baseGCDetails.gcData);
155
+ }
156
+ if (baseGCDetails.usedRoutes !== undefined) {
157
+ this.usedRoutes = Array.from(baseGCDetails.usedRoutes).sort();
158
+ this.referenceUsedRoutes = Array.from(baseGCDetails.usedRoutes).sort();
159
+ }
160
+ }
161
+
162
+ public async summarize(
163
+ fullTree: boolean,
164
+ trackState: boolean = true,
165
+ telemetryContext?: ITelemetryContext,
166
+ ): 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
168
+ // summary by calling updateUsedRoutes which sets wipSerializedUsedRoutes.
169
+ if (!this.gcDisabled && this.isTrackingInProgress()) {
170
+ assert(
171
+ this.wipSerializedUsedRoutes !== undefined,
172
+ 0x1b1 /* "wip used routes should be set if tracking a summary" */,
173
+ );
174
+ }
175
+
176
+ // If trackState is true, get summary from base summarizer node which tracks summary state.
177
+ // If trackState is false, get summary from summarizeInternal.
178
+ return trackState
179
+ ? super.summarize(fullTree, true /* trackState */, telemetryContext)
180
+ : this.summarizeFn(fullTree, trackState, telemetryContext);
181
+ }
182
+
183
+ /**
184
+ * Returns the GC data of this node. If nothing has changed since last summary, it tries to reuse the data from
185
+ * the previous summary. Else, it gets new GC data from the underlying Fluid object.
186
+ * @param fullGC - true to bypass optimizations and force full generation of GC data.
187
+ */
188
+ public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
189
+ assert(
190
+ !this.gcDisabled,
191
+ 0x1b2 /* "Getting GC data should not be called when GC is disabled!" */,
192
+ );
193
+ assert(
194
+ this.getGCDataFn !== undefined,
195
+ 0x1b3 /* "GC data cannot be retrieved without getGCDataFn" */,
196
+ );
197
+
198
+ // Load GC details from the initial summary, if not already loaded. If this is the first time this function is
199
+ // called and the node's data has not changed since last summary, the GC data in initial details is returned.
200
+ await this.loadBaseGCDetails();
201
+
202
+ // If there is no new data since last summary and we have GC data from the previous run, return it. The previous
203
+ // GC data may not be available if loaded from a snapshot with either GC disabled or before GC was added.
204
+ // Note - canReuseHandle is checked to be consistent with summarize - generate GC data for nodes for which
205
+ // summary must be generated.
206
+ if (this.canReuseHandle && !fullGC && !this.hasDataChanged() && this.gcData !== undefined) {
207
+ return cloneGCData(this.gcData);
208
+ }
209
+
210
+ const gcData = await this.getGCDataFn(fullGC);
211
+ this.gcData = cloneGCData(gcData);
212
+ return gcData;
213
+ }
214
+
215
+ /**
216
+ * Called during the start of a summary. Updates the work-in-progress used routes.
217
+ */
218
+ public startSummary(referenceSequenceNumber: number, summaryLogger: ITelemetryLogger) {
219
+ // If GC is disabled, skip setting wip used routes since we should not track GC state.
220
+ if (!this.gcDisabled) {
221
+ assert(
222
+ this.wipSerializedUsedRoutes === undefined,
223
+ 0x1b4 /* "We should not already be tracking used routes when to track a new summary" */,
224
+ );
225
+ }
226
+ super.startSummary(referenceSequenceNumber, summaryLogger);
227
+ }
228
+
229
+ /**
230
+ * Called after summary has been uploaded to the server. Add the work-in-progress state to the pending
231
+ * summary queue. We track this until we get an ack from the server for this summary.
232
+ */
233
+ protected completeSummaryCore(
234
+ proposalHandle: string,
235
+ parentPath: EscapedPath | undefined,
236
+ parentSkipRecursion: boolean,
237
+ ) {
238
+ let wipSerializedUsedRoutes: string | undefined;
239
+ // If GC is disabled, don't set wip used routes.
240
+ if (!this.gcDisabled) {
241
+ wipSerializedUsedRoutes = this.wipSerializedUsedRoutes;
242
+ assert(
243
+ wipSerializedUsedRoutes !== undefined,
244
+ 0x1b5 /* "We should have been tracking used routes" */,
245
+ );
246
+ }
247
+
248
+ super.completeSummaryCore(proposalHandle, parentPath, parentSkipRecursion);
249
+
250
+ // If GC is disabled, skip setting pending summary with GC state.
251
+ if (!this.gcDisabled) {
252
+ const summaryNode = this.pendingSummaries.get(proposalHandle);
253
+ if (summaryNode !== undefined) {
254
+ const summaryNodeWithGC = new SummaryNodeWithGC(
255
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
256
+ wipSerializedUsedRoutes!,
257
+ summaryNode,
258
+ );
259
+ this.pendingSummaries.set(proposalHandle, summaryNodeWithGC);
260
+ }
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Clears the work-in-progress state.
266
+ */
267
+ public clearSummary() {
268
+ this.wipSerializedUsedRoutes = undefined;
269
+ super.clearSummary();
270
+ }
271
+
272
+ /**
273
+ * Called when we get an ack from the server for a summary we sent. Update the reference state of this node
274
+ * from the state in the pending summary queue.
275
+ */
276
+ protected refreshLatestSummaryFromPending(
277
+ proposalHandle: string,
278
+ referenceSequenceNumber: number,
279
+ ): void {
280
+ // If GC is disabled, skip setting referenced used routes since we are not tracking GC state.
281
+ if (!this.gcDisabled) {
282
+ const summaryNode = this.pendingSummaries.get(proposalHandle) as SummaryNodeWithGC;
283
+ if (summaryNode?.serializedUsedRoutes !== undefined) {
284
+ this.referenceUsedRoutes = JSON.parse(summaryNode.serializedUsedRoutes);
285
+ }
286
+ }
287
+
288
+ return super.refreshLatestSummaryFromPending(proposalHandle, referenceSequenceNumber);
289
+ }
290
+
291
+ /**
292
+ * Called when we need to upload the reference state from the given summary.
293
+ */
294
+ protected async refreshLatestSummaryFromSnapshot(
295
+ referenceSequenceNumber: number,
296
+ snapshotTree: ISnapshotTree,
297
+ basePath: EscapedPath | undefined,
298
+ localPath: EscapedPath,
299
+ correlatedSummaryLogger: ITelemetryLogger,
300
+ readAndParseBlob: ReadAndParseBlob,
301
+ ): Promise<void> {
302
+ await this.refreshGCStateFromSnapshot(
303
+ referenceSequenceNumber,
304
+ snapshotTree,
305
+ readAndParseBlob,
306
+ );
307
+ return super.refreshLatestSummaryFromSnapshot(
308
+ referenceSequenceNumber,
309
+ snapshotTree,
310
+ basePath,
311
+ localPath,
312
+ correlatedSummaryLogger,
313
+ readAndParseBlob,
314
+ );
315
+ }
316
+
317
+ /**
318
+ * Updates GC state from the given snapshot if GC is enabled and the snapshot is newer than the one this node
319
+ * is tracking.
320
+ */
321
+ private async refreshGCStateFromSnapshot(
322
+ referenceSequenceNumber: number,
323
+ snapshotTree: ISnapshotTree,
324
+ readAndParseBlob: ReadAndParseBlob,
325
+ ): Promise<void> {
326
+ // If GC is disabled or we have seen a newer summary, skip updating GC state.
327
+ if (this.gcDisabled || this.referenceSequenceNumber >= referenceSequenceNumber) {
328
+ return;
329
+ }
330
+
331
+ // Load the base GC details before proceeding because if that happens later it can overwrite the GC details
332
+ // written by the following code.
333
+ await this.loadBaseGCDetails();
334
+
335
+ // Possible re-entrancy. We may already have processed this while loading base GC details.
336
+ if (this.referenceSequenceNumber >= referenceSequenceNumber) {
337
+ return;
338
+ }
339
+
340
+ /**
341
+ * GC data is written at root of the snapshot tree under "gc" sub-tree. This data needs to be propagated to
342
+ * all the nodes in the container.
343
+ * The root summarizer node reads the GC data from the "gc" sub-tree, runs GC on it to get used routes in
344
+ * the container and updates its GC data and referenced used routes. It then gets the GC data and used
345
+ * routes of all its children and adds it to their snapshot tree.
346
+ * All the other nodes gets the GC data and used routes from their snapshot tree and updates their state.
347
+ * They get the GC data and used routes of their children and add it to their snapshot tree and so on.
348
+ *
349
+ * Note that if the snapshot does not have GC tree, GC data will be set to undefined and used routes will be
350
+ * set to self-route (meaning referenced) for all nodes. This is important because the GC data needs to be
351
+ * regenerated in the next summary.
352
+ */
353
+ let gcDetails: IGarbageCollectionDetailsBase | undefined;
354
+ const gcSnapshotTree = snapshotTree.trees[gcTreeKey];
355
+ if (gcSnapshotTree !== undefined) {
356
+ // If there is a GC tree in the snapshot, this is the root summarizer node. Read GC data from the tree
357
+ // process it as explained above.
358
+ const gcSnapshotData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
359
+
360
+ const gcNodes: { [id: string]: string[] } = {};
361
+ for (const [nodeId, nodeData] of Object.entries(gcSnapshotData.gcState.gcNodes)) {
362
+ gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
363
+ }
364
+ // Run GC on the nodes in the snapshot to get the used routes for each node in the container.
365
+ const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
366
+ gcDetails = { gcData: { gcNodes }, usedRoutes };
367
+ } else {
368
+ // If there is a GC blob in the snapshot, it's a non-root summarizer nodes - The root summarizer node
369
+ // writes GC blob in the snapshot of child nodes. Get GC data and used routes from the blob.
370
+ const gcDetailsBlob = snapshotTree.blobs[gcTreeKey];
371
+ if (gcDetailsBlob !== undefined) {
372
+ gcDetails = JSON.parse(gcDetailsBlob) as IGarbageCollectionDetailsBase;
373
+ }
374
+ }
375
+
376
+ // Update this node to the same GC state it was when the ack corresponding to this summary was processed.
377
+ this.gcData = gcDetails?.gcData !== undefined ? cloneGCData(gcDetails.gcData) : undefined;
378
+ this.referenceUsedRoutes =
379
+ gcDetails?.usedRoutes !== undefined ? Array.from(gcDetails.usedRoutes) : undefined;
380
+ // If there are no used routes in the GC details, set it to have self route which will make the node
381
+ // referenced. This scenario can only happen if the snapshot is from a client where GC was not run or
382
+ // disabled. In both the cases, the node should be referenced.
383
+ this.usedRoutes =
384
+ gcDetails?.usedRoutes !== undefined ? Array.from(gcDetails.usedRoutes) : [""];
385
+
386
+ if (gcDetails === undefined) {
387
+ return;
388
+ }
389
+
390
+ // Generate the GC data and used routes of children GC nodes and add it to their snapshot tree.
391
+ const gcDetailsMap = unpackChildNodesGCDetails(gcDetails);
392
+ const { childrenTree } = parseSummaryForSubtrees(snapshotTree);
393
+ gcDetailsMap.forEach((childGCDetails: IGarbageCollectionDetailsBase, childId: string) => {
394
+ if (childrenTree.trees[childId] !== undefined) {
395
+ childrenTree.trees[childId].blobs[gcTreeKey] = JSON.stringify(childGCDetails);
396
+ }
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Override the createChild method to return an instance of SummarizerNodeWithGC.
402
+ */
403
+ public createChild(
404
+ /** Summarize function */
405
+ summarizeInternalFn: SummarizeInternalFn,
406
+ /** Initial id or path part of this node */
407
+ id: string,
408
+ /**
409
+ * Information needed to create the node.
410
+ * If it is from a base summary, it will assert that a summary has been seen.
411
+ * Attach information if it is created from an attach op.
412
+ */
413
+ createParam: CreateChildSummarizerNodeParam,
414
+ config: ISummarizerNodeConfigWithGC = {},
415
+ getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
416
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
417
+ ): ISummarizerNodeWithGC {
418
+ assert(!this.children.has(id), 0x1b6 /* "Create SummarizerNode child already exists" */);
419
+
420
+ /**
421
+ * Update the child node's base GC details from this node's current GC details instead of updating from the base
422
+ * GC details of this node. This will handle scenarios where the GC details was updated during refresh from
423
+ * snapshot and the child node wasn't created then. If a child is created after that, its GC details should be
424
+ * the one from the downloaded snapshot and not the base GC details.
425
+ */
426
+ const getChildBaseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
427
+ // Ensure that the base GC details is loaded because a child can be created before GC runs which is when
428
+ // base GC details is usually loaded.
429
+ await this.loadBaseGCDetails();
430
+ const childBaseGCDetails = unpackChildNodesGCDetails({
431
+ gcData: this.gcData,
432
+ usedRoutes: this.usedRoutes,
433
+ });
434
+ return childBaseGCDetails.get(id) ?? {};
435
+ });
436
+
437
+ const createDetails: ICreateChildDetails = this.getCreateDetailsForChild(id, createParam);
438
+ const child = new SummarizerNodeWithGC(
439
+ this.defaultLogger,
440
+ summarizeInternalFn,
441
+ {
442
+ ...config,
443
+ // Propagate our gcDisabled state to the child if its not explicity specified in child's config.
444
+ gcDisabled: config.gcDisabled ?? this.gcDisabled,
445
+ },
446
+ createDetails.changeSequenceNumber,
447
+ createDetails.latestSummary,
448
+ createDetails.initialSummary,
449
+ this.wipSummaryLogger,
450
+ getGCDataFn,
451
+ async () => getChildBaseGCDetailsP,
452
+ );
453
+
454
+ // There may be additional state that has to be updated in this child. For example, if a summary is being
455
+ // tracked, the child's summary tracking state needs to be updated too.
456
+ this.maybeUpdateChildState(child);
457
+
458
+ this.children.set(id, child);
459
+ return child;
460
+ }
461
+
462
+ /**
463
+ * Deletes the child node with the given id.
464
+ */
465
+ public deleteChild(id: string): void {
466
+ this.children.delete(id);
467
+ }
468
+
469
+ /**
470
+ * Override the getChild method to return an instance of SummarizerNodeWithGC.
471
+ */
472
+ public getChild(id: string): ISummarizerNodeWithGC | undefined {
473
+ return this.children.get(id) as SummarizerNodeWithGC;
474
+ }
475
+
476
+ public isReferenced(): boolean {
477
+ return this.usedRoutes.includes("") || this.usedRoutes.includes("/");
478
+ }
479
+
480
+ public updateUsedRoutes(usedRoutes: string[]) {
481
+ // Sort the given routes before updating. This will ensure that the routes compared in hasUsedStateChanged()
482
+ // are in the same order.
483
+ this.usedRoutes = usedRoutes.sort();
484
+
485
+ // If GC is not disabled and we are tracking a summary, update the work-in-progress used routes so that it can
486
+ // be tracked for this summary.
487
+ if (!this.gcDisabled && this.isTrackingInProgress()) {
488
+ this.wipSerializedUsedRoutes = JSON.stringify(this.usedRoutes);
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Override the hasChanged method. If this node data or its used state changed, the node is considered changed.
494
+ */
495
+ protected hasChanged(): boolean {
496
+ return this.hasDataChanged() || this.hasUsedStateChanged();
497
+ }
498
+
499
+ /**
500
+ * This tells whether the data in this node has changed or not.
501
+ */
502
+ private hasDataChanged(): boolean {
503
+ return super.hasChanged();
504
+ }
505
+
506
+ /**
507
+ * This tells whether the used state of this node has changed since last successful summary. If the used routes
508
+ * of this node changed, its used state is considered changed. Basically, if this node or any of its child nodes
509
+ * was previously used and became unused (or vice versa), its used state has changed.
510
+ */
511
+ private hasUsedStateChanged(): boolean {
512
+ // If GC is disabled, we are not tracking used state, return false.
513
+ if (this.gcDisabled) {
514
+ return false;
515
+ }
516
+
517
+ return (
518
+ this.referenceUsedRoutes === undefined ||
519
+ JSON.stringify(this.usedRoutes) !== JSON.stringify(this.referenceUsedRoutes)
520
+ );
521
+ }
489
522
  }
490
523
 
491
524
  /**
@@ -500,21 +533,24 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
500
533
  * @param baseGCDetailsP - Function to get the initial GC details of this node
501
534
  */
502
535
  export const createRootSummarizerNodeWithGC = (
503
- logger: ITelemetryLogger,
504
- summarizeInternalFn: SummarizeInternalFn,
505
- changeSequenceNumber: number,
506
- referenceSequenceNumber: number | undefined,
507
- config: ISummarizerNodeConfigWithGC = {},
508
- getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
509
- getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
510
- ): IRootSummarizerNodeWithGC => new SummarizerNodeWithGC(
511
- logger,
512
- summarizeInternalFn,
513
- config,
514
- changeSequenceNumber,
515
- referenceSequenceNumber === undefined ? undefined : SummaryNode.createForRoot(referenceSequenceNumber),
516
- undefined /* initialSummary */,
517
- undefined /* wipSummaryLogger */,
518
- getGCDataFn,
519
- getBaseGCDetailsFn,
520
- );
536
+ logger: ITelemetryLogger,
537
+ summarizeInternalFn: SummarizeInternalFn,
538
+ changeSequenceNumber: number,
539
+ referenceSequenceNumber: number | undefined,
540
+ config: ISummarizerNodeConfigWithGC = {},
541
+ getGCDataFn?: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
542
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
543
+ ): IRootSummarizerNodeWithGC =>
544
+ new SummarizerNodeWithGC(
545
+ logger,
546
+ summarizeInternalFn,
547
+ config,
548
+ changeSequenceNumber,
549
+ referenceSequenceNumber === undefined
550
+ ? undefined
551
+ : SummaryNode.createForRoot(referenceSequenceNumber),
552
+ undefined /* initialSummary */,
553
+ undefined /* wipSummaryLogger */,
554
+ getGCDataFn,
555
+ getBaseGCDetailsFn,
556
+ );