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