@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/.eslintrc.js +19 -8
  2. package/dist/batchTracker.d.ts +1 -2
  3. package/dist/batchTracker.d.ts.map +1 -1
  4. package/dist/batchTracker.js.map +1 -1
  5. package/dist/blobManager.d.ts +44 -33
  6. package/dist/blobManager.d.ts.map +1 -1
  7. package/dist/blobManager.js +130 -97
  8. package/dist/blobManager.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +39 -8
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +117 -61
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +1 -1
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +4 -3
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +9 -6
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +30 -24
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +41 -20
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +205 -151
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/garbageCollectionConstants.d.ts +6 -3
  26. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  27. package/dist/garbageCollectionConstants.js +7 -7
  28. package/dist/garbageCollectionConstants.js.map +1 -1
  29. package/dist/garbageCollectionTombstoneUtils.d.ts +13 -0
  30. package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  31. package/dist/garbageCollectionTombstoneUtils.js +28 -0
  32. package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
  33. package/dist/index.d.ts +0 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/opLifecycle/batchManager.d.ts +13 -1
  38. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  39. package/dist/opLifecycle/batchManager.js +35 -1
  40. package/dist/opLifecycle/batchManager.js.map +1 -1
  41. package/dist/opLifecycle/definitions.d.ts +25 -1
  42. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  43. package/dist/opLifecycle/definitions.js.map +1 -1
  44. package/dist/opLifecycle/index.d.ts +2 -2
  45. package/dist/opLifecycle/index.d.ts.map +1 -1
  46. package/dist/opLifecycle/index.js +2 -1
  47. package/dist/opLifecycle/index.js.map +1 -1
  48. package/dist/opLifecycle/opCompressor.d.ts +1 -1
  49. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  50. package/dist/opLifecycle/opCompressor.js +24 -10
  51. package/dist/opLifecycle/opCompressor.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.d.ts +2 -1
  53. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  54. package/dist/opLifecycle/opDecompressor.js +30 -17
  55. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  56. package/dist/opLifecycle/opSplitter.d.ts +34 -2
  57. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  58. package/dist/opLifecycle/opSplitter.js +114 -5
  59. package/dist/opLifecycle/opSplitter.js.map +1 -1
  60. package/dist/opLifecycle/outbox.d.ts +5 -0
  61. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  62. package/dist/opLifecycle/outbox.js +24 -14
  63. package/dist/opLifecycle/outbox.js.map +1 -1
  64. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  65. package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
  66. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  67. package/dist/packageVersion.d.ts +1 -1
  68. package/dist/packageVersion.js +1 -1
  69. package/dist/packageVersion.js.map +1 -1
  70. package/dist/runningSummarizer.d.ts.map +1 -1
  71. package/dist/runningSummarizer.js +0 -1
  72. package/dist/runningSummarizer.js.map +1 -1
  73. package/dist/scheduleManager.d.ts +0 -1
  74. package/dist/scheduleManager.d.ts.map +1 -1
  75. package/dist/scheduleManager.js +9 -20
  76. package/dist/scheduleManager.js.map +1 -1
  77. package/dist/summarizer.d.ts +0 -1
  78. package/dist/summarizer.d.ts.map +1 -1
  79. package/dist/summarizer.js +2 -1
  80. package/dist/summarizer.js.map +1 -1
  81. package/dist/summarizerTypes.d.ts +1 -0
  82. package/dist/summarizerTypes.d.ts.map +1 -1
  83. package/dist/summarizerTypes.js.map +1 -1
  84. package/dist/summaryFormat.d.ts.map +1 -1
  85. package/dist/summaryFormat.js +1 -2
  86. package/dist/summaryFormat.js.map +1 -1
  87. package/lib/batchTracker.d.ts +1 -2
  88. package/lib/batchTracker.d.ts.map +1 -1
  89. package/lib/batchTracker.js.map +1 -1
  90. package/lib/blobManager.d.ts +44 -33
  91. package/lib/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager.js +131 -98
  93. package/lib/blobManager.js.map +1 -1
  94. package/lib/containerRuntime.d.ts +39 -8
  95. package/lib/containerRuntime.d.ts.map +1 -1
  96. package/lib/containerRuntime.js +115 -59
  97. package/lib/containerRuntime.js.map +1 -1
  98. package/lib/dataStoreContext.d.ts +1 -1
  99. package/lib/dataStoreContext.d.ts.map +1 -1
  100. package/lib/dataStoreContext.js +5 -4
  101. package/lib/dataStoreContext.js.map +1 -1
  102. package/lib/dataStores.d.ts +9 -6
  103. package/lib/dataStores.d.ts.map +1 -1
  104. package/lib/dataStores.js +32 -26
  105. package/lib/dataStores.js.map +1 -1
  106. package/lib/garbageCollection.d.ts +41 -20
  107. package/lib/garbageCollection.d.ts.map +1 -1
  108. package/lib/garbageCollection.js +201 -147
  109. package/lib/garbageCollection.js.map +1 -1
  110. package/lib/garbageCollectionConstants.d.ts +6 -3
  111. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  112. package/lib/garbageCollectionConstants.js +6 -6
  113. package/lib/garbageCollectionConstants.js.map +1 -1
  114. package/lib/garbageCollectionTombstoneUtils.d.ts +13 -0
  115. package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  116. package/lib/garbageCollectionTombstoneUtils.js +24 -0
  117. package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
  118. package/lib/index.d.ts +0 -1
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +0 -1
  121. package/lib/index.js.map +1 -1
  122. package/lib/opLifecycle/batchManager.d.ts +13 -1
  123. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  124. package/lib/opLifecycle/batchManager.js +35 -1
  125. package/lib/opLifecycle/batchManager.js.map +1 -1
  126. package/lib/opLifecycle/definitions.d.ts +25 -1
  127. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  128. package/lib/opLifecycle/definitions.js.map +1 -1
  129. package/lib/opLifecycle/index.d.ts +2 -2
  130. package/lib/opLifecycle/index.d.ts.map +1 -1
  131. package/lib/opLifecycle/index.js +1 -1
  132. package/lib/opLifecycle/index.js.map +1 -1
  133. package/lib/opLifecycle/opCompressor.d.ts +1 -1
  134. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  135. package/lib/opLifecycle/opCompressor.js +24 -10
  136. package/lib/opLifecycle/opCompressor.js.map +1 -1
  137. package/lib/opLifecycle/opDecompressor.d.ts +2 -1
  138. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  139. package/lib/opLifecycle/opDecompressor.js +30 -17
  140. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  141. package/lib/opLifecycle/opSplitter.d.ts +34 -2
  142. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSplitter.js +112 -4
  144. package/lib/opLifecycle/opSplitter.js.map +1 -1
  145. package/lib/opLifecycle/outbox.d.ts +5 -0
  146. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  147. package/lib/opLifecycle/outbox.js +24 -14
  148. package/lib/opLifecycle/outbox.js.map +1 -1
  149. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  150. package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
  151. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.js +1 -1
  154. package/lib/packageVersion.js.map +1 -1
  155. package/lib/runningSummarizer.d.ts.map +1 -1
  156. package/lib/runningSummarizer.js +0 -1
  157. package/lib/runningSummarizer.js.map +1 -1
  158. package/lib/scheduleManager.d.ts +0 -1
  159. package/lib/scheduleManager.d.ts.map +1 -1
  160. package/lib/scheduleManager.js +9 -20
  161. package/lib/scheduleManager.js.map +1 -1
  162. package/lib/summarizer.d.ts +0 -1
  163. package/lib/summarizer.d.ts.map +1 -1
  164. package/lib/summarizer.js +2 -1
  165. package/lib/summarizer.js.map +1 -1
  166. package/lib/summarizerTypes.d.ts +1 -0
  167. package/lib/summarizerTypes.d.ts.map +1 -1
  168. package/lib/summarizerTypes.js.map +1 -1
  169. package/lib/summaryFormat.d.ts.map +1 -1
  170. package/lib/summaryFormat.js +1 -2
  171. package/lib/summaryFormat.js.map +1 -1
  172. package/package.json +37 -19
  173. package/src/batchTracker.ts +1 -1
  174. package/src/blobManager.ts +146 -103
  175. package/src/containerRuntime.ts +166 -65
  176. package/src/dataStoreContext.ts +5 -5
  177. package/src/dataStores.ts +40 -30
  178. package/src/garbageCollection.ts +254 -183
  179. package/src/garbageCollectionConstants.ts +7 -6
  180. package/src/garbageCollectionTombstoneUtils.ts +31 -0
  181. package/src/index.ts +0 -5
  182. package/src/opLifecycle/batchManager.ts +59 -1
  183. package/src/opLifecycle/definitions.ts +27 -1
  184. package/src/opLifecycle/index.ts +2 -1
  185. package/src/opLifecycle/opCompressor.ts +29 -12
  186. package/src/opLifecycle/opDecompressor.ts +39 -18
  187. package/src/opLifecycle/opSplitter.ts +141 -7
  188. package/src/opLifecycle/outbox.ts +32 -16
  189. package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
  190. package/src/packageVersion.ts +1 -1
  191. package/src/runningSummarizer.ts +0 -1
  192. package/src/scheduleManager.ts +19 -30
  193. package/src/summarizer.ts +1 -1
  194. package/src/summarizerTypes.ts +1 -0
  195. package/src/summaryFormat.ts +1 -2
@@ -3,28 +3,32 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger, ITelemetryPerformanceEvent } from "@fluidframework/common-definitions";
6
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
8
8
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
9
9
  import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
10
10
  import { IRequestHeader } from "@fluidframework/core-interfaces";
11
11
  import {
12
12
  cloneGCData,
13
- concatGarbageCollectionStates,
14
13
  concatGarbageCollectionData,
14
+ getGCDataFromSnapshot,
15
15
  IGCResult,
16
16
  runGarbageCollection,
17
- unpackChildNodesGCDetails,
17
+ trimLeadingSlashes,
18
18
  } from "@fluidframework/garbage-collector";
19
19
  import { ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
20
20
  import {
21
- gcBlobKey,
21
+ gcTreeKey,
22
+ gcBlobPrefix,
23
+ gcTombstoneBlobKey,
22
24
  IGarbageCollectionData,
23
- IGarbageCollectionState,
24
25
  IGarbageCollectionDetailsBase,
26
+ IGarbageCollectionSnapshotData,
27
+ IGarbageCollectionState,
25
28
  ISummarizeResult,
26
29
  ITelemetryContext,
27
30
  IGarbageCollectionNodeData,
31
+ IGarbageCollectionSummaryDetailsLegacy,
28
32
  ISummaryTreeWithStats,
29
33
  } from "@fluidframework/runtime-definitions";
30
34
  import {
@@ -46,18 +50,19 @@ import {
46
50
  import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
47
51
  import { getSummaryForDatastores } from "./dataStores";
48
52
  import {
53
+ currentGCVersion,
49
54
  defaultInactiveTimeoutMs,
50
55
  defaultSessionExpiryDurationMs,
51
56
  disableSweepLogKey,
52
57
  disableTombstoneKey,
53
- gcBlobPrefix,
58
+ gcVersionUpgradeToV2Key,
54
59
  gcTestModeKey,
55
- gcTombstoneBlobKey,
56
- gcTreeKey,
57
60
  oneDayMs,
58
61
  runGCKey,
59
62
  runSessionExpiryKey,
60
63
  runSweepKey,
64
+ stableGCVersion,
65
+ throwOnTombstoneUsageKey,
61
66
  trackGCStateKey
62
67
  } from "./garbageCollectionConstants";
63
68
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
@@ -72,9 +77,6 @@ import {
72
77
  ICreateContainerMetadata,
73
78
  } from "./summaryFormat";
74
79
 
75
- /** This is the current version of garbage collection. */
76
- const GCVersion = 1;
77
-
78
80
  /** The statistics of the system state after a garbage collection run. */
79
81
  export interface IGCStats {
80
82
  /** The number of nodes in the container. */
@@ -119,7 +121,9 @@ export interface IGarbageCollectionRuntime {
119
121
  /** After GC has run, called to notify the runtime of routes that are used in it. */
120
122
  updateUsedRoutes(usedRoutes: string[]): void;
121
123
  /** After GC has run, called to notify the runtime of routes that are unused in it. */
122
- updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean): void;
124
+ updateUnusedRoutes(unusedRoutes: string[]): void;
125
+ /** Called to notify the runtime of routes that are tombstones. */
126
+ updateTombstonedRoutes(tombstoneRoutes: string[]): void;
123
127
  /** Returns a referenced timestamp to be used to track unreferenced nodes. */
124
128
  getCurrentReferenceTimestampMs(): number | undefined;
125
129
  /** Returns the type of the GC node. */
@@ -149,10 +153,15 @@ export interface IGarbageCollector {
149
153
  ): ISummarizeResult | undefined;
150
154
  /** Returns the garbage collector specific metadata to be written into the summary. */
151
155
  getMetadata(): IGCMetadata;
152
- /** Returns a map of each node id to its base GC details in the base summary. */
153
- getBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>>;
156
+ /** Returns the GC details generated from the base snapshot. */
157
+ getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
154
158
  /** Called when the latest summary of the system has been refreshed. */
155
- latestSummaryStateRefreshed(result: RefreshSummaryResult, readAndParseBlob: ReadAndParseBlob): Promise<void>;
159
+ refreshLatestSummary(
160
+ result: RefreshSummaryResult,
161
+ proposalHandle: string | undefined,
162
+ summaryRefSeq: number,
163
+ readAndParseBlob: ReadAndParseBlob,
164
+ ): Promise<void>;
156
165
  /** Called when a node is updated. Used to detect and log when an inactive node is changed or loaded. */
157
166
  nodeUpdated(
158
167
  nodePath: string,
@@ -219,14 +228,6 @@ interface IGCSummaryTrackingData {
219
228
  serializedTombstones: string | undefined;
220
229
  }
221
230
 
222
- /**
223
- * The GC data that is read from a snapshot. It contains the GC state and tombstone state.
224
- */
225
- interface IGCSnapshotData {
226
- gcState: IGarbageCollectionState;
227
- tombstones: string[] | undefined;
228
- }
229
-
230
231
  /**
231
232
  * Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
232
233
  * be deleted by the sweep phase.
@@ -347,14 +348,16 @@ export class GarbageCollector implements IGarbageCollector {
347
348
  *
348
349
  * 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
349
350
  *
350
- * 3. The GC version in the latest summary is different from the current GC version. This can happen if:
351
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
351
352
  *
352
- * 3.1. The summary this client loaded with has data from a different GC version.
353
+ * 4. The GC version in the latest summary is different from the current GC version. This can happen if:
353
354
  *
354
- * 3.2. This client's latest summary was updated from a snapshot that has a different GC version.
355
+ * 4.1. The summary this client loaded with has data from a different GC version.
356
+ *
357
+ * 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
355
358
  */
356
359
  public get summaryStateNeedsReset(): boolean {
357
- return this.initialStateNeedsReset ||
360
+ return this.gcStateNeedsReset ||
358
361
  (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
359
362
  }
360
363
 
@@ -387,7 +390,7 @@ export class GarbageCollector implements IGarbageCollector {
387
390
  private readonly mc: MonitoringContext;
388
391
 
389
392
  /**
390
- * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
393
+ * Tells whether the GC state needs to be reset. This can happen under 3 conditions:
391
394
  *
392
395
  * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
393
396
  * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
@@ -395,18 +398,24 @@ export class GarbageCollector implements IGarbageCollector {
395
398
  * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
396
399
  * a document and the first time GC is enabled after is was disabled before.
397
400
  *
398
- * Note that the state needs reset only for the very first time summary is generated by this client. After that, the
399
- * state will be up-to-date and this flag will be reset.
401
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
402
+ *
403
+ * Note that the state will be reset only once for the first summary generated after this returns true. After that,
404
+ * this will return false.
400
405
  */
401
- private initialStateNeedsReset: boolean = false;
406
+ private get gcStateNeedsReset(): boolean {
407
+ return this.wasGCRunInLatestSummary !== this.shouldRunGC;
408
+ }
409
+ // Tracks whether there was GC was run in latest summary being tracked.
410
+ private wasGCRunInLatestSummary: boolean;
402
411
 
403
412
  // The current GC version that this container is running.
404
- private readonly currentGCVersion = GCVersion;
413
+ private readonly currentGCVersion: GCVersion;
405
414
  // This is the version of GC data in the latest summary being tracked.
406
415
  private latestSummaryGCVersion: GCVersion;
407
416
 
408
417
  // Keeps track of the GC state from the last run.
409
- private previousGCDataFromLastRun: IGarbageCollectionData | undefined;
418
+ private gcDataFromLastRun: IGarbageCollectionData | undefined;
410
419
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
411
420
  // outbound routes from that node.
412
421
  private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
@@ -422,11 +431,11 @@ export class GarbageCollector implements IGarbageCollector {
422
431
  private pendingSummaryData: IGCSummaryTrackingData | undefined;
423
432
 
424
433
  // Promise when resolved returns the GC data data in the base snapshot.
425
- private readonly baseSnapshotDataP: Promise<IGCSnapshotData | undefined>;
434
+ private readonly baseSnapshotDataP: Promise<IGarbageCollectionSnapshotData | undefined>;
426
435
  // Promise when resolved initializes the GC state from the data in the base snapshot.
427
436
  private readonly initializeGCStateFromBaseSnapshotP: Promise<void>;
428
- // The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
429
- private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
437
+ // The GC details generated from the base snapshot.
438
+ private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
430
439
  // Map of node ids to their unreferenced state tracker.
431
440
  private readonly unreferencedNodesState: Map<string, UnreferencedStateTracker> = new Map();
432
441
  // The Timer responsible for closing the container when the session has expired
@@ -497,6 +506,10 @@ export class GarbageCollector implements IGarbageCollector {
497
506
  createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } },
498
507
  ));
499
508
 
509
+ // If version upgrade is not enabled, fall back to the stable GC version.
510
+ this.currentGCVersion =
511
+ this.mc.config.getBoolean(gcVersionUpgradeToV2Key) === true ? currentGCVersion : stableGCVersion;
512
+
500
513
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(
501
514
  createParams.getContainerDiagnosticId(),
502
515
  this.mc,
@@ -630,14 +643,12 @@ export class GarbageCollector implements IGarbageCollector {
630
643
  // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
631
644
  this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
632
645
 
633
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
634
- // contain GC tree and GC is enabled.
635
- const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
636
- this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
646
+ // If GC ran in the container that generated the base snapshot, it will have a GC tree.
647
+ this.wasGCRunInLatestSummary = baseSnapshot?.trees[gcTreeKey] !== undefined;
637
648
 
638
649
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
639
650
  // it involves fetching blobs from storage which is expensive.
640
- this.baseSnapshotDataP = new LazyPromise<IGCSnapshotData | undefined>(async () => {
651
+ this.baseSnapshotDataP = new LazyPromise<IGarbageCollectionSnapshotData | undefined>(async () => {
641
652
  if (baseSnapshot === undefined) {
642
653
  return undefined;
643
654
  }
@@ -660,12 +671,12 @@ export class GarbageCollector implements IGarbageCollector {
660
671
  assert(dataStoreSnapshotTree !== undefined,
661
672
  0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
662
673
  for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
663
- const blobId = dsSnapshotTree.blobs[gcBlobKey];
674
+ const blobId = dsSnapshotTree.blobs[gcTreeKey];
664
675
  if (blobId === undefined) {
665
676
  continue;
666
677
  }
667
678
 
668
- const gcSummaryDetails = await readAndParseBlob<IGarbageCollectionDetailsBase>(blobId);
679
+ const gcSummaryDetails = await readAndParseBlob<IGarbageCollectionSummaryDetailsLegacy>(blobId);
669
680
  // If there are no nodes for this data store, skip it.
670
681
  if (gcSummaryDetails.gcData?.gcNodes === undefined) {
671
682
  continue;
@@ -710,7 +721,6 @@ export class GarbageCollector implements IGarbageCollector {
710
721
  * GC state and updates their inactive or sweep ready state.
711
722
  */
712
723
  this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
713
- const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
714
724
  /**
715
725
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
716
726
  * how long objects have been unreferenced and if they can be deleted.
@@ -719,6 +729,7 @@ export class GarbageCollector implements IGarbageCollector {
719
729
  * for this container and it is in read mode. In this scenario, there is no point in running GC anyway
720
730
  * because references in the container do not change without any ops, i.e., there is nothing to collect.
721
731
  */
732
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
722
733
  if (currentReferenceTimestampMs === undefined) {
723
734
  // Log an event so we can evaluate how often we run into this scenario.
724
735
  this.mc.logger.sendErrorEvent({
@@ -727,50 +738,25 @@ export class GarbageCollector implements IGarbageCollector {
727
738
  });
728
739
  return;
729
740
  }
730
-
731
- const baseSnapshotData = await this.baseSnapshotDataP;
732
741
  /**
733
742
  * The base snapshot data will not be present if the container is loaded from:
734
743
  * 1. The first summary created by the detached container.
735
744
  * 2. A summary that was generated with GC disabled.
736
745
  * 3. A summary that was generated before GC even existed.
737
746
  */
747
+ const baseSnapshotData = await this.baseSnapshotDataP;
738
748
  if (baseSnapshotData === undefined) {
739
749
  return;
740
750
  }
741
-
742
- const gcNodes: { [id: string]: string[]; } = {};
743
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
744
- if (nodeData.unreferencedTimestampMs !== undefined) {
745
- this.unreferencedNodesState.set(
746
- nodeId,
747
- new UnreferencedStateTracker(
748
- nodeData.unreferencedTimestampMs,
749
- this.inactiveTimeoutMs,
750
- currentReferenceTimestampMs,
751
- this.sweepTimeoutMs,
752
- ),
753
- );
754
- }
755
- gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
756
- }
757
- this.previousGCDataFromLastRun = { gcNodes };
758
-
759
- // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
760
- if (this.trackGCState) {
761
- this.latestSummaryData = {
762
- serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
763
- serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
764
- };
765
- }
751
+ this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
766
752
  });
767
753
 
768
- // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
769
- // which the caller uses to initialize each node's GC state.
770
- this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
754
+ // Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
755
+ // used to initialize the GC state of all the nodes in the container.
756
+ this.baseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
771
757
  const baseSnapshotData = await this.baseSnapshotDataP;
772
758
  if (baseSnapshotData === undefined) {
773
- return new Map();
759
+ return {};
774
760
  }
775
761
 
776
762
  const gcNodes: { [id: string]: string[]; } = {};
@@ -782,18 +768,7 @@ export class GarbageCollector implements IGarbageCollector {
782
768
  // each node in the summary.
783
769
  const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
784
770
 
785
- const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
786
- // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
787
- // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
788
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
789
- if (nodeData.unreferencedTimestampMs !== undefined) {
790
- const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
791
- if (dataStoreGCDetails !== undefined) {
792
- dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
793
- }
794
- }
795
- }
796
- return baseGCDetailsMap;
771
+ return { gcData: { gcNodes }, usedRoutes };
797
772
  });
798
773
 
799
774
  // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
@@ -823,8 +798,79 @@ export class GarbageCollector implements IGarbageCollector {
823
798
  if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
824
799
  return;
825
800
  }
826
- this.tombstones = baseSnapshotData.tombstones;
827
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
801
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
802
+ this.runtime.updateTombstonedRoutes(this.tombstones);
803
+ }
804
+
805
+ /**
806
+ * Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
807
+ * All current tracking is reset and updated from the data in the snapshot.
808
+ * @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
809
+ * is reset.
810
+ * @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
811
+ * timestamp.
812
+ */
813
+ private updateStateFromSnapshotData(
814
+ snapshotData: IGarbageCollectionSnapshotData | undefined,
815
+ currentReferenceTimestampMs: number,
816
+ ) {
817
+ /**
818
+ * Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
819
+ * snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
820
+ * its refreshing state from a summary that happened at seq#900. In this case, there may be references between
821
+ * seq#901 and seq#1000 that we don't want to reset.
822
+ * Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
823
+ * references here. This should be fine because, in the worst case, we may end up updating the unreferenced
824
+ * timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
825
+ * scenarios, so it should be okay.
826
+ */
827
+
828
+ // Clear all existing unreferenced state tracking.
829
+ for (const [, nodeStateTracker] of this.unreferencedNodesState) {
830
+ nodeStateTracker.stopTracking();
831
+ };
832
+ this.unreferencedNodesState.clear();
833
+
834
+ // If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
835
+ // container as per the state in the snapshot data.
836
+ if (this.tombstoneMode) {
837
+ this.tombstones = snapshotData?.tombstones ? Array.from(snapshotData.tombstones) : [];
838
+ this.runtime.updateTombstonedRoutes(this.tombstones);
839
+ }
840
+
841
+ // If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
842
+ if (snapshotData === undefined) {
843
+ this.gcDataFromLastRun = undefined;
844
+ this.latestSummaryData = undefined;
845
+ return;
846
+ }
847
+
848
+ // Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
849
+ // to the GC data from the snapshot data.
850
+ const gcNodes: { [id: string]: string[]; } = {};
851
+ for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
852
+ if (nodeData.unreferencedTimestampMs !== undefined) {
853
+ this.unreferencedNodesState.set(
854
+ nodeId,
855
+ new UnreferencedStateTracker(
856
+ nodeData.unreferencedTimestampMs,
857
+ this.inactiveTimeoutMs,
858
+ currentReferenceTimestampMs,
859
+ this.sweepTimeoutMs,
860
+ ),
861
+ );
862
+ }
863
+ gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
864
+ }
865
+ this.gcDataFromLastRun = { gcNodes };
866
+
867
+ // If tracking state across summaries, update latest summary data from the snapshot's GC data.
868
+ if (this.trackGCState) {
869
+ this.latestSummaryData = {
870
+ serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
871
+ serializedTombstones: JSON.stringify(snapshotData.tombstones),
872
+ };
873
+ }
828
874
  }
829
875
 
830
876
  /**
@@ -935,12 +981,12 @@ export class GarbageCollector implements IGarbageCollector {
935
981
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
936
982
  // involving access to deleted data.
937
983
  if (this.testMode) {
938
- this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
984
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
939
985
  } else if (this.tombstoneMode) {
940
- // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
941
- // scenarios involving access to "deleted" data without actually deleting the data from summaries.
942
- // Note: we will not tombstone in test mode
943
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
986
+ // If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
987
+ // involving access to "deleted" data without actually deleting the data from summaries.
988
+ // Note: we will not tombstone in test mode.
989
+ this.runtime.updateTombstonedRoutes(this.tombstones);
944
990
  }
945
991
 
946
992
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
@@ -961,12 +1007,12 @@ export class GarbageCollector implements IGarbageCollector {
961
1007
  trackState: boolean,
962
1008
  telemetryContext?: ITelemetryContext,
963
1009
  ): ISummarizeResult | undefined {
964
- if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
1010
+ if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
965
1011
  return;
966
1012
  }
967
1013
 
968
1014
  const gcState: IGarbageCollectionState = { gcNodes: {} };
969
- for (const [nodeId, outboundRoutes] of Object.entries(this.previousGCDataFromLastRun.gcNodes)) {
1015
+ for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
970
1016
  gcState.gcNodes[nodeId] = {
971
1017
  outboundRoutes,
972
1018
  unreferencedTimestampMs: this.unreferencedNodesState.get(nodeId)?.unreferencedTimestampMs,
@@ -1060,38 +1106,47 @@ export class GarbageCollector implements IGarbageCollector {
1060
1106
  }
1061
1107
 
1062
1108
  /**
1063
- * Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
1064
- * to initialize the GC state of the nodes.
1109
+ * Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
1110
+ * in the container.
1065
1111
  */
1066
- public async getBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>> {
1112
+ public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
1067
1113
  return this.baseGCDetailsP;
1068
1114
  }
1069
1115
 
1070
1116
  /**
1071
- * Called when the latest summary of the system has been refreshed. This will be used to update the state of the
1072
- * latest summary tracked.
1117
+ * Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
1118
+ * is downloaded and should be used to update the state.
1073
1119
  */
1074
- public async latestSummaryStateRefreshed(
1120
+ public async refreshLatestSummary(
1075
1121
  result: RefreshSummaryResult,
1122
+ proposalHandle: string | undefined,
1123
+ summaryRefSeq: number,
1076
1124
  readAndParseBlob: ReadAndParseBlob,
1077
1125
  ): Promise<void> {
1078
- if (!this.shouldRunGC || !result.latestSummaryUpdated) {
1126
+ // If the latest summary was updated and the summary was tracked, this client is the one that generated this
1127
+ // summary. So, update wasGCRunInLatestSummary.
1128
+ // Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
1129
+ // true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
1130
+ if (result.latestSummaryUpdated && result.wasSummaryTracked) {
1131
+ this.wasGCRunInLatestSummary = this.shouldRunGC;
1132
+ }
1133
+
1134
+ if (!result.latestSummaryUpdated || !this.shouldRunGC) {
1079
1135
  return;
1080
1136
  }
1081
1137
 
1082
1138
  // If the summary was tracked by this client, it was the one that generated the summary in the first place.
1083
- // Basically, it was written in the current GC version.
1139
+ // Update latest state from pending.
1084
1140
  if (result.wasSummaryTracked) {
1085
1141
  this.latestSummaryGCVersion = this.currentGCVersion;
1086
- this.initialStateNeedsReset = false;
1087
1142
  if (this.trackGCState) {
1088
1143
  this.latestSummaryData = this.pendingSummaryData;
1089
1144
  this.pendingSummaryData = undefined;
1090
1145
  }
1091
1146
  return;
1092
1147
  }
1093
- // If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
1094
- // result as that is now the latest summary.
1148
+
1149
+ // If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
1095
1150
  const snapshot = result.snapshot;
1096
1151
  const metadataBlobId = snapshot.blobs[metadataBlobName];
1097
1152
  if (metadataBlobId) {
@@ -1099,19 +1154,28 @@ export class GarbageCollector implements IGarbageCollector {
1099
1154
  this.latestSummaryGCVersion = getGCVersion(metadata);
1100
1155
  }
1101
1156
 
1157
+ // The current reference timestamp should be available if we are refreshing state from a snapshot. There has
1158
+ // to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
1159
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
1160
+ if (currentReferenceTimestampMs === undefined) {
1161
+ throw DataProcessingError.create(
1162
+ "No reference timestamp when updating GC state from snapshot",
1163
+ "refreshLatestSummary",
1164
+ undefined,
1165
+ { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) },
1166
+ );
1167
+ }
1102
1168
  const gcSnapshotTree = snapshot.trees[gcTreeKey];
1103
- if (gcSnapshotTree !== undefined && this.trackGCState) {
1104
- const latestGCData = await getGCDataFromSnapshot(
1169
+ // If GC ran in the container that generated this snapshot, it will have a GC tree.
1170
+ this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
1171
+ let latestGCData: IGarbageCollectionSnapshotData | undefined;
1172
+ if (gcSnapshotTree !== undefined) {
1173
+ latestGCData = await getGCDataFromSnapshot(
1105
1174
  gcSnapshotTree,
1106
1175
  readAndParseBlob,
1107
1176
  );
1108
- this.latestSummaryData = {
1109
- serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
1110
- serializedTombstones: JSON.stringify(latestGCData.tombstones),
1111
- };
1112
- } else {
1113
- this.latestSummaryData = undefined;
1114
1177
  }
1178
+ this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
1115
1179
  this.pendingSummaryData = undefined;
1116
1180
  }
1117
1181
 
@@ -1168,6 +1232,25 @@ export class GarbageCollector implements IGarbageCollector {
1168
1232
  if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
1169
1233
  this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
1170
1234
  }
1235
+
1236
+ if (this.tombstones.includes(toNodePath)) {
1237
+ const nodeType = this.runtime.getNodeType(toNodePath)
1238
+
1239
+ let eventName = "GC_Tombstone_SubDatastore_Revived";
1240
+ if (nodeType === GCNodeType.DataStore) {
1241
+ eventName = "GC_Tombstone_Datastore_Revived";
1242
+ } else if (nodeType === GCNodeType.Blob) {
1243
+ eventName = "GC_Tombstone_Blob_Revived";
1244
+ }
1245
+
1246
+ this.mc.logger.sendTelemetryEvent({
1247
+ eventName,
1248
+ isSummarizerClient: this.isSummarizerClient,
1249
+ url: trimLeadingSlashes(toNodePath),
1250
+ nodeType,
1251
+ throwOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey) ?? false,
1252
+ });
1253
+ }
1171
1254
  }
1172
1255
 
1173
1256
  public dispose(): void {
@@ -1189,7 +1272,7 @@ export class GarbageCollector implements IGarbageCollector {
1189
1272
  gcResult: IGCResult,
1190
1273
  currentReferenceTimestampMs: number,
1191
1274
  ) {
1192
- this.previousGCDataFromLastRun = cloneGCData(gcData);
1275
+ this.gcDataFromLastRun = cloneGCData(gcData);
1193
1276
  this.tombstones = [];
1194
1277
  this.newReferencesSinceLastRun.clear();
1195
1278
 
@@ -1235,33 +1318,37 @@ export class GarbageCollector implements IGarbageCollector {
1235
1318
 
1236
1319
  /**
1237
1320
  * Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
1238
- * time. It's possible that nodes transition from `unreferenced -> referenced -> unreferenced` between two runs. The
1239
- * unreferenced timestamp of such nodes needs to be reset as they may have been accessed when they were referenced.
1321
+ * time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
1322
+ * updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
1323
+ * these objects while there can be in-memory referenced to it:
1324
+ * 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
1325
+ * added, the object may have been accessed and in-memory reference to it added.
1326
+ * 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
1327
+ * unreferenced, they could have been accessed and in-memory reference to them added.
1240
1328
  *
1241
1329
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
1242
1330
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
1243
1331
  */
1244
1332
  private updateStateSinceLastRun(currentGCData: IGarbageCollectionData, logger: ITelemetryLogger) {
1245
1333
  // If we haven't run GC before there is nothing to do.
1246
- if (this.previousGCDataFromLastRun === undefined) {
1334
+ if (this.gcDataFromLastRun === undefined) {
1247
1335
  return;
1248
1336
  }
1249
1337
 
1250
1338
  // Find any references that haven't been identified correctly.
1251
1339
  const missingExplicitReferences = this.findMissingExplicitReferences(
1252
1340
  currentGCData,
1253
- this.previousGCDataFromLastRun,
1341
+ this.gcDataFromLastRun,
1254
1342
  this.newReferencesSinceLastRun,
1255
1343
  );
1256
1344
 
1257
1345
  if (missingExplicitReferences.length > 0) {
1258
1346
  missingExplicitReferences.forEach((missingExplicitReference) => {
1259
- const event: ITelemetryPerformanceEvent = {
1347
+ logger.sendErrorEvent({
1260
1348
  eventName: "gcUnknownOutboundReferences",
1261
1349
  gcNodeId: missingExplicitReference[0],
1262
1350
  gcRoutes: JSON.stringify(missingExplicitReference[1]),
1263
- };
1264
- logger.sendPerformanceEvent(event);
1351
+ });
1265
1352
  });
1266
1353
  }
1267
1354
 
@@ -1277,41 +1364,41 @@ export class GarbageCollector implements IGarbageCollector {
1277
1364
  * run, and then add the references since last run.
1278
1365
  *
1279
1366
  * Note on why we need to combine the data from previous run, current run and all references in between -
1280
- *
1281
1367
  * 1. We need data from last run because some of its references may have been deleted since then. If those
1282
- * references added new outbound references before getting deleted, we need to detect them.
1368
+ * references added new outbound references before they were deleted, we need to detect them.
1283
1369
  *
1284
1370
  * 2. We need new outbound references since last run because some of them may have been deleted later. If those
1285
- * references added new outbound references before getting deleted, we need to detect them.
1371
+ * references added new outbound references before they were deleted, we need to detect them.
1286
1372
  *
1287
1373
  * 3. We need data from the current run because currently we may not detect when DDSes are referenced:
1288
- *
1289
- * - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
1290
- * which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
1291
- *
1374
+ * - We don't require DDSes handles to be stored in a referenced DDS.
1292
1375
  * - A new data store may have "root" DDSes already created and we don't detect them today.
1293
1376
  */
1294
- const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
1377
+ const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
1378
+ const newOutboundRoutesSinceLastRun: string[] = [];
1295
1379
  this.newReferencesSinceLastRun.forEach((outboundRoutes: string[], sourceNodeId: string) => {
1296
1380
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
1297
1381
  gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
1298
1382
  } else {
1299
1383
  gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
1300
1384
  }
1385
+ newOutboundRoutesSinceLastRun.push(...outboundRoutes);
1301
1386
  });
1302
1387
 
1303
1388
  /**
1304
- * Run GC on the above reference graph to find all nodes that are referenced. For each one, if they are
1389
+ * Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
1390
+ * list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
1305
1391
  * unreferenced, stop tracking them and remove from unreferenced list.
1306
- * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
1392
+ * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
1393
+ * unreferenced and add unreferenced state.
1307
1394
  */
1308
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
1395
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
1309
1396
  for (const nodeId of gcResult.referencedNodeIds) {
1310
1397
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
1311
1398
  if (nodeStateTracker !== undefined) {
1312
1399
  // Stop tracking so as to clear out any running timers.
1313
1400
  nodeStateTracker.stopTracking();
1314
- // Delete the node as we don't need to track it any more.
1401
+ // Delete the unreferenced state as we don't need to track it any more.
1315
1402
  this.unreferencedNodesState.delete(nodeId);
1316
1403
  }
1317
1404
  }
@@ -1346,17 +1433,20 @@ export class GarbageCollector implements IGarbageCollector {
1346
1433
  const previousRoutes = previousGCData.gcNodes[nodeId] ?? [];
1347
1434
  const explicitRoutes = explicitReferences.get(nodeId) ?? [];
1348
1435
  const missingExplicitRoutes: string[] = [];
1436
+
1437
+ /**
1438
+ * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
1439
+ * explicit references should be added to missing explicit routes list.
1440
+ * 2. Only include data store and blob routes since GC only works for these two.
1441
+ * Note: Due to a bug with de-duped blobs, only adding data store routes for now.
1442
+ * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
1443
+ * explicit routes to them.
1444
+ */
1349
1445
  currentOutboundRoutes.forEach((route) => {
1350
- const isBlobOrDataStoreRoute =
1351
- this.runtime.getNodeType(route) === GCNodeType.Blob ||
1352
- this.runtime.getNodeType(route) === GCNodeType.DataStore;
1353
- // Ignore implicitly added DDS routes to their parent datastores
1354
- const notRouteFromDDSToParentDataStore = !nodeId.startsWith(route);
1355
- if (
1356
- isBlobOrDataStoreRoute &&
1357
- notRouteFromDDSToParentDataStore &&
1358
- (!previousRoutes.includes(route) && !explicitRoutes.includes(route))
1359
- ) {
1446
+ const nodeType = this.runtime.getNodeType(route);
1447
+ if ((nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob)
1448
+ && !nodeId.startsWith(route)
1449
+ && (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
1360
1450
  missingExplicitRoutes.push(route);
1361
1451
  }
1362
1452
  });
@@ -1391,7 +1481,7 @@ export class GarbageCollector implements IGarbageCollector {
1391
1481
  gcStats.nodeCount++;
1392
1482
  // If there is no previous GC data, every node's state is generated and is considered as updated.
1393
1483
  // Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
1394
- const stateUpdated = this.previousGCDataFromLastRun === undefined ||
1484
+ const stateUpdated = this.gcDataFromLastRun === undefined ||
1395
1485
  this.unreferencedNodesState.has(nodeId) === referenced;
1396
1486
  if (stateUpdated) {
1397
1487
  gcStats.updatedNodeCount++;
@@ -1530,12 +1620,20 @@ export class GarbageCollector implements IGarbageCollector {
1530
1620
  // Events generated:
1531
1621
  // InactiveObject_Loaded, SweepReadyObject_Loaded
1532
1622
  if (usageType === "Loaded") {
1533
- this.mc.logger.sendErrorEvent({
1623
+ const event = {
1534
1624
  ...propsToLog,
1535
1625
  eventName: `${state}Object_${usageType}`,
1536
1626
  pkg: packagePathToTelemetryProperty(packagePath),
1537
1627
  stack: generateStack(),
1538
- });
1628
+ };
1629
+
1630
+ // Do not log the inactive object x events as error events as they are not the best signal for
1631
+ // detecting something wrong with GC either from the partner or from the runtime itself.
1632
+ if (state === UnreferencedState.Inactive) {
1633
+ this.mc.logger.sendTelemetryEvent(event);
1634
+ } else {
1635
+ this.mc.logger.sendErrorEvent(event);
1636
+ }
1539
1637
  }
1540
1638
 
1541
1639
  // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
@@ -1566,51 +1664,24 @@ export class GarbageCollector implements IGarbageCollector {
1566
1664
  if ((usageType === "Revived") === active) {
1567
1665
  const pkg = await this.getNodePackagePath(eventProps.id);
1568
1666
  const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
1569
- logger.sendErrorEvent({
1667
+ const event = {
1570
1668
  ...propsToLog,
1571
1669
  eventName: `${state}Object_${usageType}`,
1572
1670
  pkg: pkg ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined,
1573
1671
  fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined,
1574
- });
1672
+ }
1673
+
1674
+ if (state === UnreferencedState.Inactive) {
1675
+ logger.sendTelemetryEvent(event);
1676
+ } else {
1677
+ logger.sendErrorEvent(event);
1678
+ }
1575
1679
  }
1576
1680
  }
1577
1681
  this.pendingEventsQueue = [];
1578
1682
  }
1579
1683
  }
1580
1684
 
1581
- /**
1582
- * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1583
- * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1584
- */
1585
- async function getGCDataFromSnapshot(
1586
- gcSnapshotTree: ISnapshotTree,
1587
- readAndParseBlob: ReadAndParseBlob,
1588
- ): Promise<IGCSnapshotData> {
1589
- let rootGCState: IGarbageCollectionState = { gcNodes: {} };
1590
- let tombstones: string[] | undefined;
1591
- for (const key of Object.keys(gcSnapshotTree.blobs)) {
1592
- if (key === gcTombstoneBlobKey) {
1593
- tombstones = await readAndParseBlob<string[]>(gcSnapshotTree.blobs[key]);
1594
- continue;
1595
- }
1596
-
1597
- // Skip blobs that do not start with the GC prefix.
1598
- if (!key.startsWith(gcBlobPrefix)) {
1599
- continue;
1600
- }
1601
-
1602
- const blobId = gcSnapshotTree.blobs[key];
1603
- if (blobId === undefined) {
1604
- continue;
1605
- }
1606
- const gcState = await readAndParseBlob<IGarbageCollectionState>(blobId);
1607
- assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
1608
- // Merge the GC state of this blob into the root GC state.
1609
- rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1610
- }
1611
- return { gcState: rootGCState, tombstones };
1612
- }
1613
-
1614
1685
  function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
1615
1686
  const sortableArray: [string, IGarbageCollectionNodeData][] = Object.entries(gcState.gcNodes);
1616
1687
  sortableArray.sort(([a], [b]) => a.localeCompare(b));