@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.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 (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 +45 -34
  6. package/dist/blobManager.d.ts.map +1 -1
  7. package/dist/blobManager.js +135 -102
  8. package/dist/blobManager.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +54 -8
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +143 -72
  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 +6 -8
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +12 -9
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +41 -35
  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 -150
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/garbageCollectionConstants.d.ts +7 -3
  26. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  27. package/dist/garbageCollectionConstants.js +10 -8
  28. package/dist/garbageCollectionConstants.js.map +1 -1
  29. package/dist/garbageCollectionTombstoneUtils.d.ts +14 -0
  30. package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  31. package/dist/garbageCollectionTombstoneUtils.js +23 -0
  32. package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
  33. package/dist/index.d.ts +1 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -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 +45 -34
  91. package/lib/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager.js +137 -104
  93. package/lib/blobManager.js.map +1 -1
  94. package/lib/containerRuntime.d.ts +54 -8
  95. package/lib/containerRuntime.d.ts.map +1 -1
  96. package/lib/containerRuntime.js +140 -69
  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 +7 -9
  101. package/lib/dataStoreContext.js.map +1 -1
  102. package/lib/dataStores.d.ts +12 -9
  103. package/lib/dataStores.d.ts.map +1 -1
  104. package/lib/dataStores.js +44 -38
  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 -146
  109. package/lib/garbageCollection.js.map +1 -1
  110. package/lib/garbageCollectionConstants.d.ts +7 -3
  111. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  112. package/lib/garbageCollectionConstants.js +9 -7
  113. package/lib/garbageCollectionConstants.js.map +1 -1
  114. package/lib/garbageCollectionTombstoneUtils.d.ts +14 -0
  115. package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  116. package/lib/garbageCollectionTombstoneUtils.js +19 -0
  117. package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
  118. package/lib/index.d.ts +1 -2
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +1 -2
  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 +20 -19
  173. package/src/batchTracker.ts +1 -1
  174. package/src/blobManager.ts +159 -111
  175. package/src/containerRuntime.ts +202 -73
  176. package/src/dataStoreContext.ts +15 -16
  177. package/src/dataStores.ts +61 -45
  178. package/src/garbageCollection.ts +258 -183
  179. package/src/garbageCollectionConstants.ts +10 -7
  180. package/src/garbageCollectionTombstoneUtils.ts +28 -0
  181. package/src/index.ts +2 -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
@@ -15,18 +15,17 @@ var __rest = (this && this.__rest) || function (s, e) {
15
15
  };
16
16
  import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
17
17
  import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
18
- import { cloneGCData, concatGarbageCollectionStates, concatGarbageCollectionData, runGarbageCollection, unpackChildNodesGCDetails, } from "@fluidframework/garbage-collector";
18
+ import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot, runGarbageCollection, trimLeadingSlashes, } from "@fluidframework/garbage-collector";
19
19
  import { SummaryType } from "@fluidframework/protocol-definitions";
20
- import { gcBlobKey, } from "@fluidframework/runtime-definitions";
20
+ import { gcTreeKey, gcBlobPrefix, gcTombstoneBlobKey, } from "@fluidframework/runtime-definitions";
21
21
  import { mergeStats, packagePathToTelemetryProperty, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
22
22
  import { ChildLogger, generateStack, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
23
23
  import { RuntimeHeaders } from "./containerRuntime";
24
24
  import { getSummaryForDatastores } from "./dataStores";
25
- import { defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcBlobPrefix, gcTestModeKey, gcTombstoneBlobKey, gcTreeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, trackGCStateKey } from "./garbageCollectionConstants";
25
+ import { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, trackGCStateKey } from "./garbageCollectionConstants";
26
+ import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
26
27
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
27
28
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
28
- /** This is the current version of garbage collection. */
29
- const GCVersion = 1;
30
29
  /** The types of GC nodes in the GC reference graph. */
31
30
  export const GCNodeType = {
32
31
  // Nodes that are for data stores.
@@ -139,21 +138,6 @@ export class UnreferencedStateTracker {
139
138
  export class GarbageCollector {
140
139
  constructor(createParams) {
141
140
  var _a, _b, _c, _d, _e, _f, _g, _h;
142
- /**
143
- * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
144
- *
145
- * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
146
- * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
147
- *
148
- * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
149
- * a document and the first time GC is enabled after is was disabled before.
150
- *
151
- * Note that the state needs reset only for the very first time summary is generated by this client. After that, the
152
- * state will be up-to-date and this flag will be reset.
153
- */
154
- this.initialStateNeedsReset = false;
155
- // The current GC version that this container is running.
156
- this.currentGCVersion = GCVersion;
157
141
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
158
142
  // outbound routes from that node.
159
143
  this.newReferencesSinceLastRun = new Map();
@@ -178,6 +162,9 @@ export class GarbageCollector {
178
162
  const metadata = createParams.metadata;
179
163
  const readAndParseBlob = createParams.readAndParseBlob;
180
164
  this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
165
+ // If version upgrade is not enabled, fall back to the stable GC version.
166
+ this.currentGCVersion =
167
+ this.mc.config.getBoolean(gcVersionUpgradeToV2Key) === true ? currentGCVersion : stableGCVersion;
181
168
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
182
169
  let prevSummaryGCVersion;
183
170
  /**
@@ -282,10 +269,8 @@ export class GarbageCollector {
282
269
  this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
283
270
  // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
284
271
  this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
285
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
286
- // contain GC tree and GC is enabled.
287
- const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
288
- this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
272
+ // If GC ran in the container that generated the base snapshot, it will have a GC tree.
273
+ this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
289
274
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
290
275
  // it involves fetching blobs from storage which is expensive.
291
276
  this.baseSnapshotDataP = new LazyPromise(async () => {
@@ -306,7 +291,7 @@ export class GarbageCollector {
306
291
  const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
307
292
  assert(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
308
293
  for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
309
- const blobId = dsSnapshotTree.blobs[gcBlobKey];
294
+ const blobId = dsSnapshotTree.blobs[gcTreeKey];
310
295
  if (blobId === undefined) {
311
296
  continue;
312
297
  }
@@ -347,7 +332,6 @@ export class GarbageCollector {
347
332
  * GC state and updates their inactive or sweep ready state.
348
333
  */
349
334
  this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
350
- const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
351
335
  /**
352
336
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
353
337
  * how long objects have been unreferenced and if they can be deleted.
@@ -356,6 +340,7 @@ export class GarbageCollector {
356
340
  * for this container and it is in read mode. In this scenario, there is no point in running GC anyway
357
341
  * because references in the container do not change without any ops, i.e., there is nothing to collect.
358
342
  */
343
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
359
344
  if (currentReferenceTimestampMs === undefined) {
360
345
  // Log an event so we can evaluate how often we run into this scenario.
361
346
  this.mc.logger.sendErrorEvent({
@@ -364,38 +349,24 @@ export class GarbageCollector {
364
349
  });
365
350
  return;
366
351
  }
367
- const baseSnapshotData = await this.baseSnapshotDataP;
368
352
  /**
369
353
  * The base snapshot data will not be present if the container is loaded from:
370
354
  * 1. The first summary created by the detached container.
371
355
  * 2. A summary that was generated with GC disabled.
372
356
  * 3. A summary that was generated before GC even existed.
373
357
  */
358
+ const baseSnapshotData = await this.baseSnapshotDataP;
374
359
  if (baseSnapshotData === undefined) {
375
360
  return;
376
361
  }
377
- const gcNodes = {};
378
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
379
- if (nodeData.unreferencedTimestampMs !== undefined) {
380
- this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
381
- }
382
- gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
383
- }
384
- this.previousGCDataFromLastRun = { gcNodes };
385
- // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
386
- if (this.trackGCState) {
387
- this.latestSummaryData = {
388
- serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
389
- serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
390
- };
391
- }
362
+ this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
392
363
  });
393
- // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
394
- // which the caller uses to initialize each node's GC state.
364
+ // Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
365
+ // used to initialize the GC state of all the nodes in the container.
395
366
  this.baseGCDetailsP = new LazyPromise(async () => {
396
367
  const baseSnapshotData = await this.baseSnapshotDataP;
397
368
  if (baseSnapshotData === undefined) {
398
- return new Map();
369
+ return {};
399
370
  }
400
371
  const gcNodes = {};
401
372
  for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
@@ -405,18 +376,7 @@ export class GarbageCollector {
405
376
  // This is an optimization for space (vs performance) wherein we don't need to store the used routes of
406
377
  // each node in the summary.
407
378
  const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
408
- const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
409
- // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
410
- // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
411
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
412
- if (nodeData.unreferencedTimestampMs !== undefined) {
413
- const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
414
- if (dataStoreGCDetails !== undefined) {
415
- dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
416
- }
417
- }
418
- }
419
- return baseGCDetailsMap;
379
+ return { gcData: { gcNodes }, usedRoutes };
420
380
  });
421
381
  // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
422
382
  // summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
@@ -437,16 +397,35 @@ export class GarbageCollector {
437
397
  *
438
398
  * 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
439
399
  *
440
- * 3. The GC version in the latest summary is different from the current GC version. This can happen if:
400
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
401
+ *
402
+ * 4. The GC version in the latest summary is different from the current GC version. This can happen if:
441
403
  *
442
- * 3.1. The summary this client loaded with has data from a different GC version.
404
+ * 4.1. The summary this client loaded with has data from a different GC version.
443
405
  *
444
- * 3.2. This client's latest summary was updated from a snapshot that has a different GC version.
406
+ * 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
445
407
  */
446
408
  get summaryStateNeedsReset() {
447
- return this.initialStateNeedsReset ||
409
+ return this.gcStateNeedsReset ||
448
410
  (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
449
411
  }
412
+ /**
413
+ * Tells whether the GC state needs to be reset. This can happen under 3 conditions:
414
+ *
415
+ * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
416
+ * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
417
+ *
418
+ * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
419
+ * a document and the first time GC is enabled after is was disabled before.
420
+ *
421
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
422
+ *
423
+ * Note that the state will be reset only once for the first summary generated after this returns true. After that,
424
+ * this will return false.
425
+ */
426
+ get gcStateNeedsReset() {
427
+ return this.wasGCRunInLatestSummary !== this.shouldRunGC;
428
+ }
450
429
  /** Returns a list of all the configurations for garbage collection. */
451
430
  get configs() {
452
431
  return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, testMode: this.testMode, tombstoneMode: this.tombstoneMode, sessionExpiry: this.sessionExpiryTimeoutMs, sweepTimeout: this.sweepTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
@@ -468,8 +447,63 @@ export class GarbageCollector {
468
447
  if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
469
448
  return;
470
449
  }
471
- this.tombstones = baseSnapshotData.tombstones;
472
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
450
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
451
+ this.runtime.updateTombstonedRoutes(this.tombstones);
452
+ }
453
+ /**
454
+ * Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
455
+ * All current tracking is reset and updated from the data in the snapshot.
456
+ * @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
457
+ * is reset.
458
+ * @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
459
+ * timestamp.
460
+ */
461
+ updateStateFromSnapshotData(snapshotData, currentReferenceTimestampMs) {
462
+ /**
463
+ * Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
464
+ * snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
465
+ * its refreshing state from a summary that happened at seq#900. In this case, there may be references between
466
+ * seq#901 and seq#1000 that we don't want to reset.
467
+ * Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
468
+ * references here. This should be fine because, in the worst case, we may end up updating the unreferenced
469
+ * timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
470
+ * scenarios, so it should be okay.
471
+ */
472
+ // Clear all existing unreferenced state tracking.
473
+ for (const [, nodeStateTracker] of this.unreferencedNodesState) {
474
+ nodeStateTracker.stopTracking();
475
+ }
476
+ ;
477
+ this.unreferencedNodesState.clear();
478
+ // If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
479
+ // container as per the state in the snapshot data.
480
+ if (this.tombstoneMode) {
481
+ this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
482
+ this.runtime.updateTombstonedRoutes(this.tombstones);
483
+ }
484
+ // If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
485
+ if (snapshotData === undefined) {
486
+ this.gcDataFromLastRun = undefined;
487
+ this.latestSummaryData = undefined;
488
+ return;
489
+ }
490
+ // Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
491
+ // to the GC data from the snapshot data.
492
+ const gcNodes = {};
493
+ for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
494
+ if (nodeData.unreferencedTimestampMs !== undefined) {
495
+ this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
496
+ }
497
+ gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
498
+ }
499
+ this.gcDataFromLastRun = { gcNodes };
500
+ // If tracking state across summaries, update latest summary data from the snapshot's GC data.
501
+ if (this.trackGCState) {
502
+ this.latestSummaryData = {
503
+ serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
504
+ serializedTombstones: JSON.stringify(snapshotData.tombstones),
505
+ };
506
+ }
473
507
  }
474
508
  /**
475
509
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -555,13 +589,13 @@ export class GarbageCollector {
555
589
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
556
590
  // involving access to deleted data.
557
591
  if (this.testMode) {
558
- this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
592
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
559
593
  }
560
594
  else if (this.tombstoneMode) {
561
- // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
562
- // scenarios involving access to "deleted" data without actually deleting the data from summaries.
563
- // Note: we will not tombstone in test mode
564
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
595
+ // If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
596
+ // involving access to "deleted" data without actually deleting the data from summaries.
597
+ // Note: we will not tombstone in test mode.
598
+ this.runtime.updateTombstonedRoutes(this.tombstones);
565
599
  }
566
600
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
567
601
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -576,11 +610,11 @@ export class GarbageCollector {
576
610
  */
577
611
  summarize(fullTree, trackState, telemetryContext) {
578
612
  var _a;
579
- if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
613
+ if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
580
614
  return;
581
615
  }
582
616
  const gcState = { gcNodes: {} };
583
- for (const [nodeId, outboundRoutes] of Object.entries(this.previousGCDataFromLastRun.gcNodes)) {
617
+ for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
584
618
  gcState.gcNodes[nodeId] = {
585
619
  outboundRoutes,
586
620
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
@@ -664,50 +698,58 @@ export class GarbageCollector {
664
698
  };
665
699
  }
666
700
  /**
667
- * Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
668
- * to initialize the GC state of the nodes.
701
+ * Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
702
+ * in the container.
669
703
  */
670
704
  async getBaseGCDetails() {
671
705
  return this.baseGCDetailsP;
672
706
  }
673
707
  /**
674
- * Called when the latest summary of the system has been refreshed. This will be used to update the state of the
675
- * latest summary tracked.
708
+ * Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
709
+ * is downloaded and should be used to update the state.
676
710
  */
677
- async latestSummaryStateRefreshed(result, readAndParseBlob) {
678
- if (!this.shouldRunGC || !result.latestSummaryUpdated) {
711
+ async refreshLatestSummary(result, proposalHandle, summaryRefSeq, readAndParseBlob) {
712
+ // If the latest summary was updated and the summary was tracked, this client is the one that generated this
713
+ // summary. So, update wasGCRunInLatestSummary.
714
+ // Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
715
+ // true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
716
+ if (result.latestSummaryUpdated && result.wasSummaryTracked) {
717
+ this.wasGCRunInLatestSummary = this.shouldRunGC;
718
+ }
719
+ if (!result.latestSummaryUpdated || !this.shouldRunGC) {
679
720
  return;
680
721
  }
681
722
  // If the summary was tracked by this client, it was the one that generated the summary in the first place.
682
- // Basically, it was written in the current GC version.
723
+ // Update latest state from pending.
683
724
  if (result.wasSummaryTracked) {
684
725
  this.latestSummaryGCVersion = this.currentGCVersion;
685
- this.initialStateNeedsReset = false;
686
726
  if (this.trackGCState) {
687
727
  this.latestSummaryData = this.pendingSummaryData;
688
728
  this.pendingSummaryData = undefined;
689
729
  }
690
730
  return;
691
731
  }
692
- // If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
693
- // result as that is now the latest summary.
732
+ // If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
694
733
  const snapshot = result.snapshot;
695
734
  const metadataBlobId = snapshot.blobs[metadataBlobName];
696
735
  if (metadataBlobId) {
697
736
  const metadata = await readAndParseBlob(metadataBlobId);
698
737
  this.latestSummaryGCVersion = getGCVersion(metadata);
699
738
  }
700
- const gcSnapshotTree = snapshot.trees[gcTreeKey];
701
- if (gcSnapshotTree !== undefined && this.trackGCState) {
702
- const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
703
- this.latestSummaryData = {
704
- serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
705
- serializedTombstones: JSON.stringify(latestGCData.tombstones),
706
- };
739
+ // The current reference timestamp should be available if we are refreshing state from a snapshot. There has
740
+ // to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
741
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
742
+ if (currentReferenceTimestampMs === undefined) {
743
+ throw DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) });
707
744
  }
708
- else {
709
- this.latestSummaryData = undefined;
745
+ const gcSnapshotTree = snapshot.trees[gcTreeKey];
746
+ // If GC ran in the container that generated this snapshot, it will have a GC tree.
747
+ this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
748
+ let latestGCData;
749
+ if (gcSnapshotTree !== undefined) {
750
+ latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
710
751
  }
752
+ this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
711
753
  this.pendingSummaryData = undefined;
712
754
  }
713
755
  /**
@@ -746,6 +788,23 @@ export class GarbageCollector {
746
788
  if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
747
789
  this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
748
790
  }
791
+ if (this.tombstones.includes(toNodePath)) {
792
+ const nodeType = this.runtime.getNodeType(toNodePath);
793
+ let eventName = "GC_Tombstone_SubDatastore_Revived";
794
+ if (nodeType === GCNodeType.DataStore) {
795
+ eventName = "GC_Tombstone_Datastore_Revived";
796
+ }
797
+ else if (nodeType === GCNodeType.Blob) {
798
+ eventName = "GC_Tombstone_Blob_Revived";
799
+ }
800
+ sendGCTombstoneEvent(this.mc, {
801
+ eventName,
802
+ category: "generic",
803
+ isSummarizerClient: this.isSummarizerClient,
804
+ url: trimLeadingSlashes(toNodePath),
805
+ nodeType,
806
+ }, undefined /* packagePath */);
807
+ }
749
808
  }
750
809
  dispose() {
751
810
  var _a;
@@ -762,7 +821,7 @@ export class GarbageCollector {
762
821
  * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
763
822
  */
764
823
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
765
- this.previousGCDataFromLastRun = cloneGCData(gcData);
824
+ this.gcDataFromLastRun = cloneGCData(gcData);
766
825
  this.tombstones = [];
767
826
  this.newReferencesSinceLastRun.clear();
768
827
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
@@ -798,27 +857,31 @@ export class GarbageCollector {
798
857
  }
799
858
  /**
800
859
  * Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
801
- * time. It's possible that nodes transition from `unreferenced -> referenced -> unreferenced` between two runs. The
802
- * unreferenced timestamp of such nodes needs to be reset as they may have been accessed when they were referenced.
860
+ * time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
861
+ * updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
862
+ * these objects while there can be in-memory referenced to it:
863
+ * 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
864
+ * added, the object may have been accessed and in-memory reference to it added.
865
+ * 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
866
+ * unreferenced, they could have been accessed and in-memory reference to them added.
803
867
  *
804
868
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
805
869
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
806
870
  */
807
871
  updateStateSinceLastRun(currentGCData, logger) {
808
872
  // If we haven't run GC before there is nothing to do.
809
- if (this.previousGCDataFromLastRun === undefined) {
873
+ if (this.gcDataFromLastRun === undefined) {
810
874
  return;
811
875
  }
812
876
  // Find any references that haven't been identified correctly.
813
- const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
877
+ const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
814
878
  if (missingExplicitReferences.length > 0) {
815
879
  missingExplicitReferences.forEach((missingExplicitReference) => {
816
- const event = {
880
+ logger.sendErrorEvent({
817
881
  eventName: "gcUnknownOutboundReferences",
818
882
  gcNodeId: missingExplicitReference[0],
819
883
  gcRoutes: JSON.stringify(missingExplicitReference[1]),
820
- };
821
- logger.sendPerformanceEvent(event);
884
+ });
822
885
  });
823
886
  }
824
887
  // No references were added since the last run so we don't have to update reference states of any unreferenced
@@ -832,21 +895,18 @@ export class GarbageCollector {
832
895
  * run, and then add the references since last run.
833
896
  *
834
897
  * Note on why we need to combine the data from previous run, current run and all references in between -
835
- *
836
898
  * 1. We need data from last run because some of its references may have been deleted since then. If those
837
- * references added new outbound references before getting deleted, we need to detect them.
899
+ * references added new outbound references before they were deleted, we need to detect them.
838
900
  *
839
901
  * 2. We need new outbound references since last run because some of them may have been deleted later. If those
840
- * references added new outbound references before getting deleted, we need to detect them.
902
+ * references added new outbound references before they were deleted, we need to detect them.
841
903
  *
842
904
  * 3. We need data from the current run because currently we may not detect when DDSes are referenced:
843
- *
844
- * - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
845
- * which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
846
- *
905
+ * - We don't require DDSes handles to be stored in a referenced DDS.
847
906
  * - A new data store may have "root" DDSes already created and we don't detect them today.
848
907
  */
849
- const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
908
+ const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
909
+ const newOutboundRoutesSinceLastRun = [];
850
910
  this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
851
911
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
852
912
  gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
@@ -854,19 +914,22 @@ export class GarbageCollector {
854
914
  else {
855
915
  gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
856
916
  }
917
+ newOutboundRoutesSinceLastRun.push(...outboundRoutes);
857
918
  });
858
919
  /**
859
- * Run GC on the above reference graph to find all nodes that are referenced. For each one, if they are
920
+ * Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
921
+ * list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
860
922
  * unreferenced, stop tracking them and remove from unreferenced list.
861
- * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
923
+ * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
924
+ * unreferenced and add unreferenced state.
862
925
  */
863
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
926
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
864
927
  for (const nodeId of gcResult.referencedNodeIds) {
865
928
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
866
929
  if (nodeStateTracker !== undefined) {
867
930
  // Stop tracking so as to clear out any running timers.
868
931
  nodeStateTracker.stopTracking();
869
- // Delete the node as we don't need to track it any more.
932
+ // Delete the unreferenced state as we don't need to track it any more.
870
933
  this.unreferencedNodesState.delete(nodeId);
871
934
  }
872
935
  }
@@ -893,14 +956,19 @@ export class GarbageCollector {
893
956
  const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
894
957
  const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
895
958
  const missingExplicitRoutes = [];
959
+ /**
960
+ * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
961
+ * explicit references should be added to missing explicit routes list.
962
+ * 2. Only include data store and blob routes since GC only works for these two.
963
+ * Note: Due to a bug with de-duped blobs, only adding data store routes for now.
964
+ * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
965
+ * explicit routes to them.
966
+ */
896
967
  currentOutboundRoutes.forEach((route) => {
897
- const isBlobOrDataStoreRoute = this.runtime.getNodeType(route) === GCNodeType.Blob ||
898
- this.runtime.getNodeType(route) === GCNodeType.DataStore;
899
- // Ignore implicitly added DDS routes to their parent datastores
900
- const notRouteFromDDSToParentDataStore = !nodeId.startsWith(route);
901
- if (isBlobOrDataStoreRoute &&
902
- notRouteFromDDSToParentDataStore &&
903
- (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
968
+ const nodeType = this.runtime.getNodeType(route);
969
+ if ((nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob)
970
+ && !nodeId.startsWith(route)
971
+ && (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
904
972
  missingExplicitRoutes.push(route);
905
973
  }
906
974
  });
@@ -932,7 +1000,7 @@ export class GarbageCollector {
932
1000
  gcStats.nodeCount++;
933
1001
  // If there is no previous GC data, every node's state is generated and is considered as updated.
934
1002
  // Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
935
- const stateUpdated = this.previousGCDataFromLastRun === undefined ||
1003
+ const stateUpdated = this.gcDataFromLastRun === undefined ||
936
1004
  this.unreferencedNodesState.has(nodeId) === referenced;
937
1005
  if (stateUpdated) {
938
1006
  gcStats.updatedNodeCount++;
@@ -1039,7 +1107,15 @@ export class GarbageCollector {
1039
1107
  // Events generated:
1040
1108
  // InactiveObject_Loaded, SweepReadyObject_Loaded
1041
1109
  if (usageType === "Loaded") {
1042
- this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() }));
1110
+ const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() });
1111
+ // Do not log the inactive object x events as error events as they are not the best signal for
1112
+ // detecting something wrong with GC either from the partner or from the runtime itself.
1113
+ if (state === UnreferencedState.Inactive) {
1114
+ this.mc.logger.sendTelemetryEvent(event);
1115
+ }
1116
+ else {
1117
+ this.mc.logger.sendErrorEvent(event);
1118
+ }
1043
1119
  }
1044
1120
  // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
1045
1121
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
@@ -1068,39 +1144,18 @@ export class GarbageCollector {
1068
1144
  if ((usageType === "Revived") === active) {
1069
1145
  const pkg = await this.getNodePackagePath(eventProps.id);
1070
1146
  const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
1071
- logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined, fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined }));
1147
+ const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined, fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined });
1148
+ if (state === UnreferencedState.Inactive) {
1149
+ logger.sendTelemetryEvent(event);
1150
+ }
1151
+ else {
1152
+ logger.sendErrorEvent(event);
1153
+ }
1072
1154
  }
1073
1155
  }
1074
1156
  this.pendingEventsQueue = [];
1075
1157
  }
1076
1158
  }
1077
- /**
1078
- * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1079
- * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1080
- */
1081
- async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1082
- let rootGCState = { gcNodes: {} };
1083
- let tombstones;
1084
- for (const key of Object.keys(gcSnapshotTree.blobs)) {
1085
- if (key === gcTombstoneBlobKey) {
1086
- tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1087
- continue;
1088
- }
1089
- // Skip blobs that do not start with the GC prefix.
1090
- if (!key.startsWith(gcBlobPrefix)) {
1091
- continue;
1092
- }
1093
- const blobId = gcSnapshotTree.blobs[key];
1094
- if (blobId === undefined) {
1095
- continue;
1096
- }
1097
- const gcState = await readAndParseBlob(blobId);
1098
- assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
1099
- // Merge the GC state of this blob into the root GC state.
1100
- rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1101
- }
1102
- return { gcState: rootGCState, tombstones };
1103
- }
1104
1159
  function generateSortedGCState(gcState) {
1105
1160
  const sortableArray = Object.entries(gcState.gcNodes);
1106
1161
  sortableArray.sort(([a], [b]) => a.localeCompare(b));