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