@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723

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 (168) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/batchManager.d.ts +11 -6
  3. package/dist/batchManager.d.ts.map +1 -1
  4. package/dist/batchManager.js +23 -13
  5. package/dist/batchManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +74 -20
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +190 -137
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +50 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +36 -19
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +207 -121
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  28. package/dist/gcSweepReadyUsageDetection.js +3 -12
  29. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  30. package/dist/index.d.ts +4 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/opCompressor.d.ts +18 -0
  35. package/dist/opCompressor.d.ts.map +1 -0
  36. package/dist/opCompressor.js +50 -0
  37. package/dist/opCompressor.js.map +1 -0
  38. package/dist/opDecompressor.d.ts +20 -0
  39. package/dist/opDecompressor.d.ts.map +1 -0
  40. package/dist/opDecompressor.js +72 -0
  41. package/dist/opDecompressor.js.map +1 -0
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/pendingStateManager.d.ts +6 -26
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +42 -62
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/runningSummarizer.d.ts +3 -2
  50. package/dist/runningSummarizer.d.ts.map +1 -1
  51. package/dist/runningSummarizer.js +10 -3
  52. package/dist/runningSummarizer.js.map +1 -1
  53. package/dist/scheduleManager.js.map +1 -1
  54. package/dist/summarizer.js +7 -2
  55. package/dist/summarizer.js.map +1 -1
  56. package/dist/summarizerClientElection.js +1 -1
  57. package/dist/summarizerClientElection.js.map +1 -1
  58. package/dist/summarizerHeuristics.d.ts.map +1 -1
  59. package/dist/summarizerHeuristics.js +0 -3
  60. package/dist/summarizerHeuristics.js.map +1 -1
  61. package/dist/summarizerTypes.d.ts +19 -2
  62. package/dist/summarizerTypes.d.ts.map +1 -1
  63. package/dist/summarizerTypes.js.map +1 -1
  64. package/dist/summaryFormat.d.ts +4 -2
  65. package/dist/summaryFormat.d.ts.map +1 -1
  66. package/dist/summaryFormat.js.map +1 -1
  67. package/dist/summaryGenerator.d.ts.map +1 -1
  68. package/dist/summaryGenerator.js +3 -2
  69. package/dist/summaryGenerator.js.map +1 -1
  70. package/dist/summaryManager.d.ts.map +1 -1
  71. package/dist/summaryManager.js +10 -6
  72. package/dist/summaryManager.js.map +1 -1
  73. package/garbageCollection.md +27 -22
  74. package/lib/batchManager.d.ts +11 -6
  75. package/lib/batchManager.d.ts.map +1 -1
  76. package/lib/batchManager.js +23 -13
  77. package/lib/batchManager.js.map +1 -1
  78. package/lib/containerRuntime.d.ts +74 -20
  79. package/lib/containerRuntime.d.ts.map +1 -1
  80. package/lib/containerRuntime.js +189 -136
  81. package/lib/containerRuntime.js.map +1 -1
  82. package/lib/dataStore.d.ts.map +1 -1
  83. package/lib/dataStore.js +6 -0
  84. package/lib/dataStore.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +14 -21
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js +75 -61
  88. package/lib/dataStoreContext.js.map +1 -1
  89. package/lib/dataStoreContexts.js +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +11 -10
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js +53 -23
  94. package/lib/dataStores.js.map +1 -1
  95. package/lib/garbageCollection.d.ts +36 -19
  96. package/lib/garbageCollection.d.ts.map +1 -1
  97. package/lib/garbageCollection.js +207 -121
  98. package/lib/garbageCollection.js.map +1 -1
  99. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  100. package/lib/gcSweepReadyUsageDetection.js +3 -12
  101. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  102. package/lib/index.d.ts +4 -6
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +2 -4
  105. package/lib/index.js.map +1 -1
  106. package/lib/opCompressor.d.ts +18 -0
  107. package/lib/opCompressor.d.ts.map +1 -0
  108. package/lib/opCompressor.js +46 -0
  109. package/lib/opCompressor.js.map +1 -0
  110. package/lib/opDecompressor.d.ts +20 -0
  111. package/lib/opDecompressor.d.ts.map +1 -0
  112. package/lib/opDecompressor.js +68 -0
  113. package/lib/opDecompressor.js.map +1 -0
  114. package/lib/packageVersion.d.ts +1 -1
  115. package/lib/packageVersion.js +1 -1
  116. package/lib/packageVersion.js.map +1 -1
  117. package/lib/pendingStateManager.d.ts +6 -26
  118. package/lib/pendingStateManager.d.ts.map +1 -1
  119. package/lib/pendingStateManager.js +42 -62
  120. package/lib/pendingStateManager.js.map +1 -1
  121. package/lib/runningSummarizer.d.ts +3 -2
  122. package/lib/runningSummarizer.d.ts.map +1 -1
  123. package/lib/runningSummarizer.js +10 -3
  124. package/lib/runningSummarizer.js.map +1 -1
  125. package/lib/scheduleManager.js.map +1 -1
  126. package/lib/summarizer.js +7 -2
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerClientElection.js +1 -1
  129. package/lib/summarizerClientElection.js.map +1 -1
  130. package/lib/summarizerHeuristics.d.ts.map +1 -1
  131. package/lib/summarizerHeuristics.js +0 -3
  132. package/lib/summarizerHeuristics.js.map +1 -1
  133. package/lib/summarizerTypes.d.ts +19 -2
  134. package/lib/summarizerTypes.d.ts.map +1 -1
  135. package/lib/summarizerTypes.js.map +1 -1
  136. package/lib/summaryFormat.d.ts +4 -2
  137. package/lib/summaryFormat.d.ts.map +1 -1
  138. package/lib/summaryFormat.js.map +1 -1
  139. package/lib/summaryGenerator.d.ts.map +1 -1
  140. package/lib/summaryGenerator.js +3 -2
  141. package/lib/summaryGenerator.js.map +1 -1
  142. package/lib/summaryManager.d.ts.map +1 -1
  143. package/lib/summaryManager.js +10 -6
  144. package/lib/summaryManager.js.map +1 -1
  145. package/package.json +37 -63
  146. package/prettier.config.cjs +8 -0
  147. package/src/batchManager.ts +32 -15
  148. package/src/containerRuntime.ts +260 -156
  149. package/src/dataStore.ts +13 -1
  150. package/src/dataStoreContext.ts +100 -76
  151. package/src/dataStoreContexts.ts +1 -1
  152. package/src/dataStores.ts +61 -23
  153. package/src/garbageCollection.ts +255 -126
  154. package/src/gcSweepReadyUsageDetection.ts +2 -10
  155. package/src/index.ts +4 -4
  156. package/src/opCompressor.ts +59 -0
  157. package/src/opDecompressor.ts +82 -0
  158. package/src/packageVersion.ts +1 -1
  159. package/src/pendingStateManager.ts +57 -96
  160. package/src/runningSummarizer.ts +11 -3
  161. package/src/scheduleManager.ts +1 -0
  162. package/src/summarizer.ts +6 -6
  163. package/src/summarizerClientElection.ts +1 -1
  164. package/src/summarizerHeuristics.ts +0 -3
  165. package/src/summarizerTypes.ts +20 -7
  166. package/src/summaryFormat.ts +4 -2
  167. package/src/summaryGenerator.ts +3 -2
  168. package/src/summaryManager.ts +18 -7
@@ -18,7 +18,7 @@ import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@flu
18
18
  import { cloneGCData, concatGarbageCollectionStates, concatGarbageCollectionData, runGarbageCollection, unpackChildNodesGCDetails, } from "@fluidframework/garbage-collector";
19
19
  import { SummaryType } from "@fluidframework/protocol-definitions";
20
20
  import { gcBlobKey, } from "@fluidframework/runtime-definitions";
21
- import { mergeStats, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
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";
@@ -30,22 +30,24 @@ const GCVersion = 1;
30
30
  export const gcTreeKey = "gc";
31
31
  // They prefix for GC blobs in the GC tree in summary.
32
32
  export const gcBlobPrefix = "__gc";
33
+ // The key for tombstone blob in the GC tree in summary.
34
+ export const gcTombstoneBlobKey = "__tombstones";
33
35
  // Feature gate key to turn GC on / off.
34
36
  export const runGCKey = "Fluid.GarbageCollection.RunGC";
35
37
  // Feature gate key to turn GC sweep on / off.
36
38
  export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
37
39
  // Feature gate key to turn GC test mode on / off.
38
40
  export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
39
- // Feature gate key to write GC data at the root of the summary tree.
40
- const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
41
41
  // Feature gate key to expire a session after a set period of time.
42
42
  export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
43
- // Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
44
- export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
45
43
  // Feature gate key to write the gc blob as a handle if the data is the same.
46
44
  export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
47
45
  // Feature gate key to turn GC sweep log off.
48
46
  export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
47
+ // Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
48
+ export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
49
+ // Feature gate to enable throwing an error when tombstone object is used.
50
+ export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
49
51
  // One day in milliseconds.
50
52
  export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
51
53
  export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
@@ -162,10 +164,6 @@ export class UnreferencedStateTracker {
162
164
  export class GarbageCollector {
163
165
  constructor(createParams) {
164
166
  var _a, _b, _c, _d, _e, _f, _g, _h;
165
- /**
166
- * Tells whether the GC data should be written to the root of the summary tree.
167
- */
168
- this._writeDataAtRoot = true;
169
167
  /**
170
168
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
171
169
  *
@@ -184,6 +182,7 @@ export class GarbageCollector {
184
182
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
185
183
  // outbound routes from that node.
186
184
  this.newReferencesSinceLastRun = new Map();
185
+ this.tombstones = [];
187
186
  // Map of node ids to their unreferenced state tracker.
188
187
  this.unreferencedNodesState = new Map();
189
188
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -196,6 +195,7 @@ export class GarbageCollector {
196
195
  this.runtime = createParams.runtime;
197
196
  this.isSummarizerClient = createParams.isSummarizerClient;
198
197
  this.gcOptions = createParams.gcOptions;
198
+ this.createContainerMetadata = createParams.createContainerMetadata;
199
199
  this.getNodePackagePath = createParams.getNodePackagePath;
200
200
  this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
201
201
  this.activeConnection = createParams.activeConnection;
@@ -205,6 +205,20 @@ export class GarbageCollector {
205
205
  this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
206
206
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
207
207
  let prevSummaryGCVersion;
208
+ /**
209
+ * Sweep timeout is the time after which unreferenced content can be swept.
210
+ * Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
211
+ *
212
+ * The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
213
+ * The buffer is added to account for any clock skew or other edge cases.
214
+ * We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
215
+ */
216
+ function computeSweepTimeout(sessionExpiryTimeoutMs) {
217
+ const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
218
+ const bufferMs = oneDayMs;
219
+ return sessionExpiryTimeoutMs &&
220
+ (sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
221
+ }
208
222
  /**
209
223
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
210
224
  * 1. Whether running GC mark phase is allowed or not.
@@ -219,6 +233,8 @@ export class GarbageCollector {
219
233
  this.gcEnabled = prevSummaryGCVersion > 0;
220
234
  this.sweepEnabled = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.sweepEnabled) !== null && _a !== void 0 ? _a : false;
221
235
  this.sessionExpiryTimeoutMs = metadata === null || metadata === void 0 ? void 0 : metadata.sessionExpiryTimeoutMs;
236
+ this.sweepTimeoutMs =
237
+ (_b = metadata === null || metadata === void 0 ? void 0 : metadata.sweepTimeoutMs) !== null && _b !== void 0 ? _b : computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
222
238
  }
223
239
  else {
224
240
  // Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
@@ -226,36 +242,28 @@ export class GarbageCollector {
226
242
  if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
227
243
  throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
228
244
  }
245
+ // This Test Override only applies for new containers
246
+ const testOverrideSweepTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
229
247
  // For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
230
248
  // flag in GC options to false.
231
249
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
232
250
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
233
- this.sweepEnabled = this.gcOptions.sweepAllowed === true;
251
+ // ...unless we're using the TestOverride
252
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
234
253
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
235
254
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
236
- this.sessionExpiryTimeoutMs = (_b = this.gcOptions.sessionExpiryTimeoutMs) !== null && _b !== void 0 ? _b : defaultSessionExpiryDurationMs;
255
+ this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
237
256
  }
257
+ this.sweepTimeoutMs =
258
+ testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
238
259
  }
239
260
  // If session expiry is enabled, we need to close the container when the session expiry timeout expires.
240
- if (this.sessionExpiryTimeoutMs !== undefined && this.mc.config.getBoolean(disableSessionExpiryKey) !== true) {
261
+ if (this.sessionExpiryTimeoutMs !== undefined) {
241
262
  // If Test Override config is set, override Session Expiry timeout.
242
263
  const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
243
264
  const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
244
265
  this.sessionExpiryTimer = new Timer(timeoutMs, () => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
245
266
  this.sessionExpiryTimer.start();
246
- // TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
247
- // This unblocks the Sweep Log (see logSweepEvents function).
248
- // This will be removed before sweep is fully implemented.
249
- const snapshotCacheExpiryMs = (_c = createParams.snapshotCacheExpiryMs) !== null && _c !== void 0 ? _c : 5 * 24 * 60 * 60 * 1000;
250
- /**
251
- * Sweep timeout is the time after which unreferenced content can be swept.
252
- * Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
253
- * added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
254
- * but make it one day to be safe.
255
- */
256
- if (snapshotCacheExpiryMs !== undefined) {
257
- this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + oneDayMs;
258
- }
259
267
  }
260
268
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
261
269
  // latest tracked GC version. For new documents, we will be writing the first summary with the current version.
@@ -278,36 +286,34 @@ export class GarbageCollector {
278
286
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
279
287
  *
280
288
  * 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
281
- *
282
289
  * 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
283
- *
284
- * 3. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
290
+ * 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
291
+ * the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
292
+ * 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
285
293
  * feature flag.
286
294
  */
287
- this.shouldRunSweep = false; // disable while TEMPORARY measure hardcoding snapshotCacheExpiryMs is here
288
- // this.shouldRunGC
289
- // && this.sweepTimeoutMs !== undefined
290
- // && (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
295
+ this.shouldRunSweep =
296
+ this.shouldRunGC
297
+ && this.sweepTimeoutMs !== undefined
298
+ && ((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
291
299
  this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
292
300
  // Override inactive timeout if test config or gc options to override it is set.
293
- this.inactiveTimeoutMs = (_f = (_e = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _e !== void 0 ? _e : this.gcOptions.inactiveTimeoutMs) !== null && _f !== void 0 ? _f : defaultInactiveTimeoutMs;
301
+ this.inactiveTimeoutMs = (_g = (_f = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _f !== void 0 ? _f : this.gcOptions.inactiveTimeoutMs) !== null && _g !== void 0 ? _g : defaultInactiveTimeoutMs;
294
302
  // Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
295
303
  if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
296
304
  throw new UsageError("inactive timeout should not be greater than the sweep timeout");
297
305
  }
298
306
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
299
- this.testMode = (_g = this.mc.config.getBoolean(gcTestModeKey)) !== null && _g !== void 0 ? _g : this.gcOptions.runGCInTestMode === true;
300
- // GC state is written into root of the summary tree by default. Can be overridden via feature flag for now.
301
- this._writeDataAtRoot = (_h = this.mc.config.getBoolean(writeAtRootKey)) !== null && _h !== void 0 ? _h : true;
302
- if (this._writeDataAtRoot) {
303
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
304
- // contain GC tree and GC is enabled.
305
- const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
306
- this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
307
- }
308
- // Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
309
- // this once since it involves fetching blobs from storage which is expensive.
310
- const baseSummaryStateP = new LazyPromise(async () => {
307
+ this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
308
+ // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
309
+ this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
310
+ // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
311
+ // contain GC tree and GC is enabled.
312
+ const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
313
+ this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
314
+ // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
315
+ // it involves fetching blobs from storage which is expensive.
316
+ this.baseSnapshotDataP = new LazyPromise(async () => {
311
317
  var _a;
312
318
  if (baseSnapshot === undefined) {
313
319
  return undefined;
@@ -316,13 +322,7 @@ export class GarbageCollector {
316
322
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
317
323
  const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
318
324
  if (gcSnapshotTree !== undefined) {
319
- // If the GC tree is written at root, we should also do the same.
320
- this._writeDataAtRoot = true;
321
- const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
322
- if (this.trackGCState) {
323
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
324
- }
325
- return baseGCState;
325
+ return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
326
326
  }
327
327
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
328
328
  // consolidate into IGarbageCollectionState format.
@@ -358,7 +358,7 @@ export class GarbageCollector {
358
358
  }
359
359
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
360
360
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
361
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
361
+ return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
362
362
  }
363
363
  catch (error) {
364
364
  const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -367,11 +367,11 @@ export class GarbageCollector {
367
367
  }
368
368
  });
369
369
  /**
370
- * Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
371
- * timestamp maybe from old ops which were not summarized and stored in the file. So, the unreferenced state
372
- * may be out of date. This is fine because the state is updated every time GC runs based on the time then.
370
+ * Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
371
+ * connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
372
+ * GC state and updates their inactive or sweep ready state.
373
373
  */
374
- this.initializeBaseStateP = new LazyPromise(async () => {
374
+ this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
375
375
  const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
376
376
  /**
377
377
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
@@ -389,34 +389,41 @@ export class GarbageCollector {
389
389
  });
390
390
  return;
391
391
  }
392
- const baseState = await baseSummaryStateP;
392
+ const baseSnapshotData = await this.baseSnapshotDataP;
393
393
  /**
394
- * The base state will not be present if the container is loaded from:
394
+ * The base snapshot data will not be present if the container is loaded from:
395
395
  * 1. The first summary created by the detached container.
396
396
  * 2. A summary that was generated with GC disabled.
397
397
  * 3. A summary that was generated before GC even existed.
398
398
  */
399
- if (baseState === undefined) {
399
+ if (baseSnapshotData === undefined) {
400
400
  return;
401
401
  }
402
402
  const gcNodes = {};
403
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
403
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
404
404
  if (nodeData.unreferencedTimestampMs !== undefined) {
405
405
  this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
406
406
  }
407
407
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
408
408
  }
409
409
  this.previousGCDataFromLastRun = { gcNodes };
410
+ // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
411
+ if (this.trackGCState) {
412
+ this.latestSummaryData = {
413
+ serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
414
+ serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
415
+ };
416
+ }
410
417
  });
411
418
  // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
412
419
  // which the caller uses to initialize each node's GC state.
413
420
  this.baseGCDetailsP = new LazyPromise(async () => {
414
- const baseState = await baseSummaryStateP;
415
- if (baseState === undefined) {
421
+ const baseSnapshotData = await this.baseSnapshotDataP;
422
+ if (baseSnapshotData === undefined) {
416
423
  return new Map();
417
424
  }
418
425
  const gcNodes = {};
419
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
426
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
420
427
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
421
428
  }
422
429
  // Run GC on the nodes in the base summary to get the routes used in each node in the container.
@@ -426,7 +433,7 @@ export class GarbageCollector {
426
433
  const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
427
434
  // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
428
435
  // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
429
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
436
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
430
437
  if (nodeData.unreferencedTimestampMs !== undefined) {
431
438
  const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
432
439
  if (dataStoreGCDetails !== undefined) {
@@ -465,12 +472,29 @@ export class GarbageCollector {
465
472
  return this.initialStateNeedsReset ||
466
473
  (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
467
474
  }
468
- get writeDataAtRoot() {
469
- return this._writeDataAtRoot;
470
- }
471
475
  /** Returns a list of all the configurations for garbage collection. */
472
476
  get configs() {
473
- return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, writeAtRoot: this._writeDataAtRoot, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
477
+ 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);
478
+ }
479
+ /**
480
+ * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
481
+ * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
482
+ * in use or not.
483
+ */
484
+ async initializeBaseState() {
485
+ const baseSnapshotData = await this.baseSnapshotDataP;
486
+ /**
487
+ * The base snapshot data or tombstone state will not be present if the container is loaded from:
488
+ * 1. The first summary created by the detached container.
489
+ * 2. A summary that was generated with GC disabled.
490
+ * 3. A summary that was generated before GC even existed.
491
+ * 4. A summary that was generated with tombstone feature disabled.
492
+ */
493
+ if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
494
+ return;
495
+ }
496
+ this.tombstones = baseSnapshotData.tombstones;
497
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
474
498
  }
475
499
  /**
476
500
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -480,16 +504,20 @@ export class GarbageCollector {
480
504
  */
481
505
  setConnectionState(connected, clientId) {
482
506
  /**
483
- * For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
507
+ * For all clients, initialize the base state when the container becomes active, i.e., it transitions
484
508
  * to "write" mode. This will ensure that the container's own join op is processed and there is a recent
485
509
  * reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
486
510
  * could affect the GC state will have been processed.
487
511
  *
512
+ * If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
513
+ * InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
514
+ * the receiving summarizer client.
515
+ *
488
516
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
489
517
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
490
518
  */
491
- if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
492
- this.initializeBaseStateP.catch((error) => { });
519
+ if (this.activeConnection() && this.shouldRunGC) {
520
+ this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
493
521
  }
494
522
  }
495
523
  /**
@@ -525,14 +553,14 @@ export class GarbageCollector {
525
553
  const gcData = await this.runtime.getGCData(fullGC);
526
554
  const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
527
555
  const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
528
- event.end(Object.assign({}, gcStats));
556
+ event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
529
557
  this.completedRuns++;
530
558
  return gcStats;
531
559
  }, { end: true, cancel: "error" });
532
560
  }
533
561
  async runPreGCSteps() {
534
- // Ensure that base state has been initialized.
535
- await this.initializeBaseStateP;
562
+ // Ensure that state has been initialized from the base snapshot data.
563
+ await this.initializeGCStateFromBaseSnapshotP;
536
564
  // Let the runtime update its pending state before GC runs.
537
565
  await this.runtime.updateStateBeforeGC();
538
566
  }
@@ -545,14 +573,20 @@ export class GarbageCollector {
545
573
  this.updateStateSinceLastRun(gcData, logger);
546
574
  // Update the current state and update the runtime of all routes or ids that used as per the GC run.
547
575
  this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
548
- this.runtime.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
576
+ this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
549
577
  // Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
550
578
  // delete these objects here instead.
551
579
  this.logSweepEvents(logger, currentReferenceTimestampMs);
552
580
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
553
581
  // involving access to deleted data.
554
582
  if (this.testMode) {
555
- this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
583
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
584
+ }
585
+ else if (this.tombstoneMode) {
586
+ // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
587
+ // scenarios involving access to "deleted" data without actually deleting the data from summaries.
588
+ // Note: we will not tombstone in test mode
589
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
556
590
  }
557
591
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
558
592
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -577,31 +611,69 @@ export class GarbageCollector {
577
611
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
578
612
  };
579
613
  }
580
- const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
614
+ const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
615
+ const serializedTombstones = this.tombstoneMode
616
+ ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
617
+ : undefined;
581
618
  /**
582
- * As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
583
- * instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
619
+ * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
620
+ * summary, send summary handles for them. Otherwise, send the data in summary blobs.
584
621
  */
585
622
  if (this.trackGCState) {
586
- this.pendingSerializedSummaryState = newSerializedSummaryState;
587
- if (this.latestSerializedSummaryState !== undefined &&
588
- this.latestSerializedSummaryState === newSerializedSummaryState &&
589
- !fullTree &&
590
- trackState) {
591
- const stats = mergeStats();
592
- stats.handleNodeCount++;
593
- return {
594
- summary: {
595
- type: SummaryType.Handle,
596
- handle: `/${gcTreeKey}`,
597
- handleType: SummaryType.Tree,
598
- },
599
- stats,
600
- };
623
+ this.pendingSummaryData = { serializedGCState, serializedTombstones };
624
+ if (trackState && !fullTree && this.latestSummaryData !== undefined) {
625
+ // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
626
+ if (this.latestSummaryData.serializedGCState === serializedGCState
627
+ && this.latestSummaryData.serializedTombstones === serializedTombstones) {
628
+ const stats = mergeStats();
629
+ stats.handleNodeCount++;
630
+ return {
631
+ summary: {
632
+ type: SummaryType.Handle,
633
+ handle: `/${gcTreeKey}`,
634
+ handleType: SummaryType.Tree,
635
+ },
636
+ stats,
637
+ };
638
+ }
639
+ // If either or both of GC state or tombstone state changed, build a GC summary tree.
640
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
601
641
  }
602
642
  }
643
+ // If not tracking GC state, build a GC summary tree without any summary handles.
644
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
645
+ }
646
+ /**
647
+ * Builds the GC summary tree which contains GC state and tombstone state.
648
+ * If trackState is false, both GC state and tombstone state are written as summary blobs.
649
+ * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
650
+ * @param serializedGCState - The GC state serialized as string.
651
+ * @param serializedTombstones - THe tombstone state serialized as string.
652
+ * @param trackState - Whether we are tracking GC state across summaries.
653
+ * @returns the GC summary tree.
654
+ */
655
+ buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
656
+ var _a, _b;
657
+ const gcStateBlobKey = `${gcBlobPrefix}_root`;
603
658
  const builder = new SummaryTreeBuilder();
604
- builder.addBlob(`${gcBlobPrefix}_root`, newSerializedSummaryState);
659
+ // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
660
+ if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
661
+ builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
662
+ }
663
+ else {
664
+ builder.addBlob(gcStateBlobKey, serializedGCState);
665
+ }
666
+ // If there is no tombstone data, return only the GC state.
667
+ if (serializedTombstones === undefined) {
668
+ return builder.getSummaryTree();
669
+ }
670
+ // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
671
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
672
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
673
+ }
674
+ else {
675
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
676
+ }
605
677
  return builder.getSummaryTree();
606
678
  }
607
679
  getMetadata() {
@@ -613,6 +685,7 @@ export class GarbageCollector {
613
685
  gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
614
686
  sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
615
687
  sweepEnabled: this.sweepEnabled,
688
+ sweepTimeoutMs: this.sweepTimeoutMs,
616
689
  };
617
690
  }
618
691
  /**
@@ -636,8 +709,8 @@ export class GarbageCollector {
636
709
  this.latestSummaryGCVersion = this.currentGCVersion;
637
710
  this.initialStateNeedsReset = false;
638
711
  if (this.trackGCState) {
639
- this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
640
- this.pendingSerializedSummaryState = undefined;
712
+ this.latestSummaryData = this.pendingSummaryData;
713
+ this.pendingSummaryData = undefined;
641
714
  }
642
715
  return;
643
716
  }
@@ -651,13 +724,16 @@ export class GarbageCollector {
651
724
  }
652
725
  const gcSnapshotTree = snapshot.trees[gcTreeKey];
653
726
  if (gcSnapshotTree !== undefined && this.trackGCState) {
654
- const latestGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
655
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
727
+ const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
728
+ this.latestSummaryData = {
729
+ serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
730
+ serializedTombstones: JSON.stringify(latestGCData.tombstones),
731
+ };
656
732
  }
657
733
  else {
658
- this.latestSerializedSummaryState = undefined;
734
+ this.latestSummaryData = undefined;
659
735
  }
660
- this.pendingSerializedSummaryState = undefined;
736
+ this.pendingSummaryData = undefined;
661
737
  }
662
738
  /**
663
739
  * Called when a node with the given id is updated. If the node is inactive, log an error.
@@ -712,6 +788,7 @@ export class GarbageCollector {
712
788
  */
713
789
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
714
790
  this.previousGCDataFromLastRun = cloneGCData(gcData);
791
+ this.tombstones = [];
715
792
  this.newReferencesSinceLastRun.clear();
716
793
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
717
794
  for (const nodeId of gcResult.referencedNodeIds) {
@@ -735,6 +812,12 @@ export class GarbageCollector {
735
812
  }
736
813
  else {
737
814
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
815
+ if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
816
+ const nodeType = this.runtime.getNodeType(nodeId);
817
+ if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
818
+ this.tombstones.push(nodeId);
819
+ }
820
+ }
738
821
  }
739
822
  }
740
823
  }
@@ -753,7 +836,7 @@ export class GarbageCollector {
753
836
  }
754
837
  // Find any references that haven't been identified correctly.
755
838
  const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
756
- if (this.writeDataAtRoot && missingExplicitReferences.length > 0) {
839
+ if (missingExplicitReferences.length > 0) {
757
840
  missingExplicitReferences.forEach((missingExplicitReference) => {
758
841
  const event = {
759
842
  eventName: "gcUnknownOutboundReferences",
@@ -964,31 +1047,24 @@ export class GarbageCollector {
964
1047
  return;
965
1048
  }
966
1049
  this.loggedUnreferencedEvents.add(uniqueEventId);
967
- const propsToLog = {
968
- id: nodeId,
969
- type: nodeType,
970
- unrefTime: nodeStateTracker.unreferencedTimestampMs,
971
- age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
972
- timeout: nodeStateTracker.state === UnreferencedState.Inactive
1050
+ const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === UnreferencedState.Inactive
973
1051
  ? this.inactiveTimeoutMs
974
- : this.sweepTimeoutMs,
975
- completedGCRuns: this.completedRuns,
976
- lastSummaryTime: this.getLastSummaryTimestampMs(),
977
- externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.externalRequest],
978
- viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle],
979
- fromId: fromNodeId,
980
- };
1052
+ : this.sweepTimeoutMs, completedGCRuns: this.completedRuns, lastSummaryTime: this.getLastSummaryTimestampMs() }, this.createContainerMetadata), { externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.externalRequest], viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle], fromId: fromNodeId });
981
1053
  // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
982
1054
  // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
983
1055
  // but it's a good signal nonetheless and we can consume it with a grain of salt.
1056
+ // Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
1057
+ // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
984
1058
  if (this.isSummarizerClient) {
985
1059
  this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
986
1060
  }
987
1061
  else {
988
1062
  // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
989
1063
  // summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
1064
+ // Events generated:
1065
+ // InactiveObject_Loaded, SweepReadyObject_Loaded
990
1066
  if (usageType === "Loaded") {
991
- this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePath ? { value: packagePath.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined, stack: generateStack() }));
1067
+ this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() }));
992
1068
  }
993
1069
  // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
994
1070
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
@@ -999,6 +1075,11 @@ export class GarbageCollector {
999
1075
  }
1000
1076
  }
1001
1077
  async logUnreferencedEvents(logger) {
1078
+ // Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
1079
+ // summary time they are then logged.
1080
+ // Events generated:
1081
+ // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
1082
+ // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
1002
1083
  for (const eventProps of this.pendingEventsQueue) {
1003
1084
  const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
1004
1085
  /**
@@ -1019,12 +1100,17 @@ export class GarbageCollector {
1019
1100
  }
1020
1101
  }
1021
1102
  /**
1022
- * Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
1023
- * Merge the GC state from all such blobs and return the merged GC state.
1103
+ * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1104
+ * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1024
1105
  */
1025
- async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1106
+ async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1026
1107
  let rootGCState = { gcNodes: {} };
1108
+ let tombstones;
1027
1109
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1110
+ if (key === gcTombstoneBlobKey) {
1111
+ tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1112
+ continue;
1113
+ }
1028
1114
  // Skip blobs that do not start with the GC prefix.
1029
1115
  if (!key.startsWith(gcBlobPrefix)) {
1030
1116
  continue;
@@ -1038,7 +1124,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1038
1124
  // Merge the GC state of this blob into the root GC state.
1039
1125
  rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1040
1126
  }
1041
- return rootGCState;
1127
+ return { gcState: rootGCState, tombstones };
1042
1128
  }
1043
1129
  function generateSortedGCState(gcState) {
1044
1130
  const sortableArray = Object.entries(gcState.gcNodes);