@fluidframework/container-runtime 2.0.0-dev.1.4.5.105745 → 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 +208 -122
  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 +257 -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
@@ -15,7 +15,7 @@ var __rest = (this && this.__rest) || function (s, e) {
15
15
  return t;
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.disableSweepLogKey = exports.trackGCStateKey = exports.disableSessionExpiryKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
18
+ exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.throwOnTombstoneUsageKey = exports.disableTombstoneKey = exports.disableSweepLogKey = exports.trackGCStateKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcTombstoneBlobKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
19
19
  const common_utils_1 = require("@fluidframework/common-utils");
20
20
  const container_utils_1 = require("@fluidframework/container-utils");
21
21
  const garbage_collector_1 = require("@fluidframework/garbage-collector");
@@ -33,22 +33,24 @@ const GCVersion = 1;
33
33
  exports.gcTreeKey = "gc";
34
34
  // They prefix for GC blobs in the GC tree in summary.
35
35
  exports.gcBlobPrefix = "__gc";
36
+ // The key for tombstone blob in the GC tree in summary.
37
+ exports.gcTombstoneBlobKey = "__tombstones";
36
38
  // Feature gate key to turn GC on / off.
37
39
  exports.runGCKey = "Fluid.GarbageCollection.RunGC";
38
40
  // Feature gate key to turn GC sweep on / off.
39
41
  exports.runSweepKey = "Fluid.GarbageCollection.RunSweep";
40
42
  // Feature gate key to turn GC test mode on / off.
41
43
  exports.gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
42
- // Feature gate key to write GC data at the root of the summary tree.
43
- const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
44
44
  // Feature gate key to expire a session after a set period of time.
45
45
  exports.runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
46
- // Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
47
- exports.disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
48
46
  // Feature gate key to write the gc blob as a handle if the data is the same.
49
47
  exports.trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
50
48
  // Feature gate key to turn GC sweep log off.
51
49
  exports.disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
50
+ // Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
51
+ exports.disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
52
+ // Feature gate to enable throwing an error when tombstone object is used.
53
+ exports.throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
52
54
  // One day in milliseconds.
53
55
  exports.oneDayMs = 1 * 24 * 60 * 60 * 1000;
54
56
  exports.defaultInactiveTimeoutMs = 7 * exports.oneDayMs; // 7 days
@@ -166,10 +168,6 @@ exports.UnreferencedStateTracker = UnreferencedStateTracker;
166
168
  class GarbageCollector {
167
169
  constructor(createParams) {
168
170
  var _a, _b, _c, _d, _e, _f, _g, _h;
169
- /**
170
- * Tells whether the GC data should be written to the root of the summary tree.
171
- */
172
- this._writeDataAtRoot = true;
173
171
  /**
174
172
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
175
173
  *
@@ -188,6 +186,7 @@ class GarbageCollector {
188
186
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
189
187
  // outbound routes from that node.
190
188
  this.newReferencesSinceLastRun = new Map();
189
+ this.tombstones = [];
191
190
  // Map of node ids to their unreferenced state tracker.
192
191
  this.unreferencedNodesState = new Map();
193
192
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -200,6 +199,7 @@ class GarbageCollector {
200
199
  this.runtime = createParams.runtime;
201
200
  this.isSummarizerClient = createParams.isSummarizerClient;
202
201
  this.gcOptions = createParams.gcOptions;
202
+ this.createContainerMetadata = createParams.createContainerMetadata;
203
203
  this.getNodePackagePath = createParams.getNodePackagePath;
204
204
  this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
205
205
  this.activeConnection = createParams.activeConnection;
@@ -209,6 +209,20 @@ class GarbageCollector {
209
209
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
210
210
  this.sweepReadyUsageHandler = new gcSweepReadyUsageDetection_1.SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
211
211
  let prevSummaryGCVersion;
212
+ /**
213
+ * Sweep timeout is the time after which unreferenced content can be swept.
214
+ * Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
215
+ *
216
+ * The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
217
+ * The buffer is added to account for any clock skew or other edge cases.
218
+ * We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
219
+ */
220
+ function computeSweepTimeout(sessionExpiryTimeoutMs) {
221
+ const maxSnapshotCacheExpiryMs = 5 * exports.oneDayMs;
222
+ const bufferMs = exports.oneDayMs;
223
+ return sessionExpiryTimeoutMs &&
224
+ (sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
225
+ }
212
226
  /**
213
227
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
214
228
  * 1. Whether running GC mark phase is allowed or not.
@@ -223,6 +237,8 @@ class GarbageCollector {
223
237
  this.gcEnabled = prevSummaryGCVersion > 0;
224
238
  this.sweepEnabled = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.sweepEnabled) !== null && _a !== void 0 ? _a : false;
225
239
  this.sessionExpiryTimeoutMs = metadata === null || metadata === void 0 ? void 0 : metadata.sessionExpiryTimeoutMs;
240
+ this.sweepTimeoutMs =
241
+ (_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
226
242
  }
227
243
  else {
228
244
  // Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
@@ -230,36 +246,28 @@ class GarbageCollector {
230
246
  if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
231
247
  throw new container_utils_1.UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
232
248
  }
249
+ // This Test Override only applies for new containers
250
+ const testOverrideSweepTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
233
251
  // For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
234
252
  // flag in GC options to false.
235
253
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
236
254
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
237
- this.sweepEnabled = this.gcOptions.sweepAllowed === true;
255
+ // ...unless we're using the TestOverride
256
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
238
257
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
239
258
  if (this.mc.config.getBoolean(exports.runSessionExpiryKey) && this.gcEnabled) {
240
- this.sessionExpiryTimeoutMs = (_b = this.gcOptions.sessionExpiryTimeoutMs) !== null && _b !== void 0 ? _b : exports.defaultSessionExpiryDurationMs;
259
+ this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : exports.defaultSessionExpiryDurationMs;
241
260
  }
261
+ this.sweepTimeoutMs =
262
+ testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
242
263
  }
243
264
  // If session expiry is enabled, we need to close the container when the session expiry timeout expires.
244
- if (this.sessionExpiryTimeoutMs !== undefined && this.mc.config.getBoolean(exports.disableSessionExpiryKey) !== true) {
265
+ if (this.sessionExpiryTimeoutMs !== undefined) {
245
266
  // If Test Override config is set, override Session Expiry timeout.
246
267
  const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
247
268
  const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
248
269
  this.sessionExpiryTimer = new common_utils_1.Timer(timeoutMs, () => { this.runtime.closeFn(new container_utils_1.ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
249
270
  this.sessionExpiryTimer.start();
250
- // TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
251
- // This unblocks the Sweep Log (see logSweepEvents function).
252
- // This will be removed before sweep is fully implemented.
253
- const snapshotCacheExpiryMs = (_c = createParams.snapshotCacheExpiryMs) !== null && _c !== void 0 ? _c : 5 * 24 * 60 * 60 * 1000;
254
- /**
255
- * Sweep timeout is the time after which unreferenced content can be swept.
256
- * Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
257
- * added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
258
- * but make it one day to be safe.
259
- */
260
- if (snapshotCacheExpiryMs !== undefined) {
261
- this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + exports.oneDayMs;
262
- }
263
271
  }
264
272
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
265
273
  // latest tracked GC version. For new documents, we will be writing the first summary with the current version.
@@ -282,36 +290,34 @@ class GarbageCollector {
282
290
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
283
291
  *
284
292
  * 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
285
- *
286
293
  * 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
287
- *
288
- * 3. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
294
+ * 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
295
+ * the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
296
+ * 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
289
297
  * feature flag.
290
298
  */
291
- this.shouldRunSweep = false; // disable while TEMPORARY measure hardcoding snapshotCacheExpiryMs is here
292
- // this.shouldRunGC
293
- // && this.sweepTimeoutMs !== undefined
294
- // && (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
299
+ this.shouldRunSweep =
300
+ this.shouldRunGC
301
+ && this.sweepTimeoutMs !== undefined
302
+ && ((_e = this.mc.config.getBoolean(exports.runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
295
303
  this.trackGCState = this.mc.config.getBoolean(exports.trackGCStateKey) === true;
296
304
  // Override inactive timeout if test config or gc options to override it is set.
297
- 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 : exports.defaultInactiveTimeoutMs;
305
+ 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 : exports.defaultInactiveTimeoutMs;
298
306
  // Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
299
307
  if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
300
308
  throw new container_utils_1.UsageError("inactive timeout should not be greater than the sweep timeout");
301
309
  }
302
310
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
303
- this.testMode = (_g = this.mc.config.getBoolean(exports.gcTestModeKey)) !== null && _g !== void 0 ? _g : this.gcOptions.runGCInTestMode === true;
304
- // GC state is written into root of the summary tree by default. Can be overridden via feature flag for now.
305
- this._writeDataAtRoot = (_h = this.mc.config.getBoolean(writeAtRootKey)) !== null && _h !== void 0 ? _h : true;
306
- if (this._writeDataAtRoot) {
307
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
308
- // contain GC tree and GC is enabled.
309
- const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[exports.gcTreeKey]) !== undefined;
310
- this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
311
- }
312
- // Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
313
- // this once since it involves fetching blobs from storage which is expensive.
314
- const baseSummaryStateP = new common_utils_1.LazyPromise(async () => {
311
+ this.testMode = (_h = this.mc.config.getBoolean(exports.gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
312
+ // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
313
+ this.tombstoneMode = this.mc.config.getBoolean(exports.disableTombstoneKey) !== true;
314
+ // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
315
+ // contain GC tree and GC is enabled.
316
+ const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[exports.gcTreeKey]) !== undefined;
317
+ this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
318
+ // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
319
+ // it involves fetching blobs from storage which is expensive.
320
+ this.baseSnapshotDataP = new common_utils_1.LazyPromise(async () => {
315
321
  var _a;
316
322
  if (baseSnapshot === undefined) {
317
323
  return undefined;
@@ -320,13 +326,7 @@ class GarbageCollector {
320
326
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
321
327
  const gcSnapshotTree = baseSnapshot.trees[exports.gcTreeKey];
322
328
  if (gcSnapshotTree !== undefined) {
323
- // If the GC tree is written at root, we should also do the same.
324
- this._writeDataAtRoot = true;
325
- const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
326
- if (this.trackGCState) {
327
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
328
- }
329
- return baseGCState;
329
+ return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
330
330
  }
331
331
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
332
332
  // consolidate into IGarbageCollectionState format.
@@ -362,7 +362,7 @@ class GarbageCollector {
362
362
  }
363
363
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
364
364
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
365
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
365
+ return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
366
366
  }
367
367
  catch (error) {
368
368
  const dpe = container_utils_1.DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -371,11 +371,11 @@ class GarbageCollector {
371
371
  }
372
372
  });
373
373
  /**
374
- * Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
375
- * timestamp maybe from old ops which were not summarized and stored in the file. So, the unreferenced state
376
- * may be out of date. This is fine because the state is updated every time GC runs based on the time then.
374
+ * Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
375
+ * connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
376
+ * GC state and updates their inactive or sweep ready state.
377
377
  */
378
- this.initializeBaseStateP = new common_utils_1.LazyPromise(async () => {
378
+ this.initializeGCStateFromBaseSnapshotP = new common_utils_1.LazyPromise(async () => {
379
379
  const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
380
380
  /**
381
381
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
@@ -393,34 +393,41 @@ class GarbageCollector {
393
393
  });
394
394
  return;
395
395
  }
396
- const baseState = await baseSummaryStateP;
396
+ const baseSnapshotData = await this.baseSnapshotDataP;
397
397
  /**
398
- * The base state will not be present if the container is loaded from:
398
+ * The base snapshot data will not be present if the container is loaded from:
399
399
  * 1. The first summary created by the detached container.
400
400
  * 2. A summary that was generated with GC disabled.
401
401
  * 3. A summary that was generated before GC even existed.
402
402
  */
403
- if (baseState === undefined) {
403
+ if (baseSnapshotData === undefined) {
404
404
  return;
405
405
  }
406
406
  const gcNodes = {};
407
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
407
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
408
408
  if (nodeData.unreferencedTimestampMs !== undefined) {
409
409
  this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
410
410
  }
411
411
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
412
412
  }
413
413
  this.previousGCDataFromLastRun = { gcNodes };
414
+ // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
415
+ if (this.trackGCState) {
416
+ this.latestSummaryData = {
417
+ serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
418
+ serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
419
+ };
420
+ }
414
421
  });
415
422
  // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
416
423
  // which the caller uses to initialize each node's GC state.
417
424
  this.baseGCDetailsP = new common_utils_1.LazyPromise(async () => {
418
- const baseState = await baseSummaryStateP;
419
- if (baseState === undefined) {
425
+ const baseSnapshotData = await this.baseSnapshotDataP;
426
+ if (baseSnapshotData === undefined) {
420
427
  return new Map();
421
428
  }
422
429
  const gcNodes = {};
423
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
430
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
424
431
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
425
432
  }
426
433
  // Run GC on the nodes in the base summary to get the routes used in each node in the container.
@@ -430,7 +437,7 @@ class GarbageCollector {
430
437
  const baseGCDetailsMap = (0, garbage_collector_1.unpackChildNodesGCDetails)({ gcData: { gcNodes }, usedRoutes });
431
438
  // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
432
439
  // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
433
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
440
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
434
441
  if (nodeData.unreferencedTimestampMs !== undefined) {
435
442
  const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
436
443
  if (dataStoreGCDetails !== undefined) {
@@ -469,12 +476,29 @@ class GarbageCollector {
469
476
  return this.initialStateNeedsReset ||
470
477
  (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
471
478
  }
472
- get writeDataAtRoot() {
473
- return this._writeDataAtRoot;
474
- }
475
479
  /** Returns a list of all the configurations for garbage collection. */
476
480
  get configs() {
477
- 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);
481
+ 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);
482
+ }
483
+ /**
484
+ * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
485
+ * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
486
+ * in use or not.
487
+ */
488
+ async initializeBaseState() {
489
+ const baseSnapshotData = await this.baseSnapshotDataP;
490
+ /**
491
+ * The base snapshot data or tombstone state will not be present if the container is loaded from:
492
+ * 1. The first summary created by the detached container.
493
+ * 2. A summary that was generated with GC disabled.
494
+ * 3. A summary that was generated before GC even existed.
495
+ * 4. A summary that was generated with tombstone feature disabled.
496
+ */
497
+ if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
498
+ return;
499
+ }
500
+ this.tombstones = baseSnapshotData.tombstones;
501
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
478
502
  }
479
503
  /**
480
504
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -484,16 +508,20 @@ class GarbageCollector {
484
508
  */
485
509
  setConnectionState(connected, clientId) {
486
510
  /**
487
- * For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
511
+ * For all clients, initialize the base state when the container becomes active, i.e., it transitions
488
512
  * to "write" mode. This will ensure that the container's own join op is processed and there is a recent
489
513
  * reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
490
514
  * could affect the GC state will have been processed.
491
515
  *
516
+ * If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
517
+ * InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
518
+ * the receiving summarizer client.
519
+ *
492
520
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
493
521
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
494
522
  */
495
- if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
496
- this.initializeBaseStateP.catch((error) => { });
523
+ if (this.activeConnection() && this.shouldRunGC) {
524
+ this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
497
525
  }
498
526
  }
499
527
  /**
@@ -529,14 +557,14 @@ class GarbageCollector {
529
557
  const gcData = await this.runtime.getGCData(fullGC);
530
558
  const gcResult = (0, garbage_collector_1.runGarbageCollection)(gcData.gcNodes, ["/"]);
531
559
  const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
532
- event.end(Object.assign({}, gcStats));
560
+ event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
533
561
  this.completedRuns++;
534
562
  return gcStats;
535
563
  }, { end: true, cancel: "error" });
536
564
  }
537
565
  async runPreGCSteps() {
538
- // Ensure that base state has been initialized.
539
- await this.initializeBaseStateP;
566
+ // Ensure that state has been initialized from the base snapshot data.
567
+ await this.initializeGCStateFromBaseSnapshotP;
540
568
  // Let the runtime update its pending state before GC runs.
541
569
  await this.runtime.updateStateBeforeGC();
542
570
  }
@@ -549,14 +577,20 @@ class GarbageCollector {
549
577
  this.updateStateSinceLastRun(gcData, logger);
550
578
  // Update the current state and update the runtime of all routes or ids that used as per the GC run.
551
579
  this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
552
- this.runtime.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
580
+ this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
553
581
  // Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
554
582
  // delete these objects here instead.
555
583
  this.logSweepEvents(logger, currentReferenceTimestampMs);
556
584
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
557
585
  // involving access to deleted data.
558
586
  if (this.testMode) {
559
- this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
587
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
588
+ }
589
+ else if (this.tombstoneMode) {
590
+ // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
591
+ // scenarios involving access to "deleted" data without actually deleting the data from summaries.
592
+ // Note: we will not tombstone in test mode
593
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
560
594
  }
561
595
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
562
596
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -581,31 +615,69 @@ class GarbageCollector {
581
615
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
582
616
  };
583
617
  }
584
- const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
618
+ const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
619
+ const serializedTombstones = this.tombstoneMode
620
+ ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
621
+ : undefined;
585
622
  /**
586
- * As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
587
- * instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
623
+ * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
624
+ * summary, send summary handles for them. Otherwise, send the data in summary blobs.
588
625
  */
589
626
  if (this.trackGCState) {
590
- this.pendingSerializedSummaryState = newSerializedSummaryState;
591
- if (this.latestSerializedSummaryState !== undefined &&
592
- this.latestSerializedSummaryState === newSerializedSummaryState &&
593
- !fullTree &&
594
- trackState) {
595
- const stats = (0, runtime_utils_1.mergeStats)();
596
- stats.handleNodeCount++;
597
- return {
598
- summary: {
599
- type: protocol_definitions_1.SummaryType.Handle,
600
- handle: `/${exports.gcTreeKey}`,
601
- handleType: protocol_definitions_1.SummaryType.Tree,
602
- },
603
- stats,
604
- };
627
+ this.pendingSummaryData = { serializedGCState, serializedTombstones };
628
+ if (trackState && !fullTree && this.latestSummaryData !== undefined) {
629
+ // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
630
+ if (this.latestSummaryData.serializedGCState === serializedGCState
631
+ && this.latestSummaryData.serializedTombstones === serializedTombstones) {
632
+ const stats = (0, runtime_utils_1.mergeStats)();
633
+ stats.handleNodeCount++;
634
+ return {
635
+ summary: {
636
+ type: protocol_definitions_1.SummaryType.Handle,
637
+ handle: `/${exports.gcTreeKey}`,
638
+ handleType: protocol_definitions_1.SummaryType.Tree,
639
+ },
640
+ stats,
641
+ };
642
+ }
643
+ // If either or both of GC state or tombstone state changed, build a GC summary tree.
644
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
605
645
  }
606
646
  }
647
+ // If not tracking GC state, build a GC summary tree without any summary handles.
648
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
649
+ }
650
+ /**
651
+ * Builds the GC summary tree which contains GC state and tombstone state.
652
+ * If trackState is false, both GC state and tombstone state are written as summary blobs.
653
+ * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
654
+ * @param serializedGCState - The GC state serialized as string.
655
+ * @param serializedTombstones - THe tombstone state serialized as string.
656
+ * @param trackState - Whether we are tracking GC state across summaries.
657
+ * @returns the GC summary tree.
658
+ */
659
+ buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
660
+ var _a, _b;
661
+ const gcStateBlobKey = `${exports.gcBlobPrefix}_root`;
607
662
  const builder = new runtime_utils_1.SummaryTreeBuilder();
608
- builder.addBlob(`${exports.gcBlobPrefix}_root`, newSerializedSummaryState);
663
+ // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
664
+ if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
665
+ builder.addHandle(gcStateBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${gcStateBlobKey}`);
666
+ }
667
+ else {
668
+ builder.addBlob(gcStateBlobKey, serializedGCState);
669
+ }
670
+ // If there is no tombstone data, return only the GC state.
671
+ if (serializedTombstones === undefined) {
672
+ return builder.getSummaryTree();
673
+ }
674
+ // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
675
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
676
+ builder.addHandle(exports.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${exports.gcTombstoneBlobKey}`);
677
+ }
678
+ else {
679
+ builder.addBlob(exports.gcTombstoneBlobKey, serializedTombstones);
680
+ }
609
681
  return builder.getSummaryTree();
610
682
  }
611
683
  getMetadata() {
@@ -617,6 +689,7 @@ class GarbageCollector {
617
689
  gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
618
690
  sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
619
691
  sweepEnabled: this.sweepEnabled,
692
+ sweepTimeoutMs: this.sweepTimeoutMs,
620
693
  };
621
694
  }
622
695
  /**
@@ -640,8 +713,8 @@ class GarbageCollector {
640
713
  this.latestSummaryGCVersion = this.currentGCVersion;
641
714
  this.initialStateNeedsReset = false;
642
715
  if (this.trackGCState) {
643
- this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
644
- this.pendingSerializedSummaryState = undefined;
716
+ this.latestSummaryData = this.pendingSummaryData;
717
+ this.pendingSummaryData = undefined;
645
718
  }
646
719
  return;
647
720
  }
@@ -655,13 +728,16 @@ class GarbageCollector {
655
728
  }
656
729
  const gcSnapshotTree = snapshot.trees[exports.gcTreeKey];
657
730
  if (gcSnapshotTree !== undefined && this.trackGCState) {
658
- const latestGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
659
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
731
+ const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
732
+ this.latestSummaryData = {
733
+ serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
734
+ serializedTombstones: JSON.stringify(latestGCData.tombstones),
735
+ };
660
736
  }
661
737
  else {
662
- this.latestSerializedSummaryState = undefined;
738
+ this.latestSummaryData = undefined;
663
739
  }
664
- this.pendingSerializedSummaryState = undefined;
740
+ this.pendingSummaryData = undefined;
665
741
  }
666
742
  /**
667
743
  * Called when a node with the given id is updated. If the node is inactive, log an error.
@@ -716,6 +792,7 @@ class GarbageCollector {
716
792
  */
717
793
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
718
794
  this.previousGCDataFromLastRun = (0, garbage_collector_1.cloneGCData)(gcData);
795
+ this.tombstones = [];
719
796
  this.newReferencesSinceLastRun.clear();
720
797
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
721
798
  for (const nodeId of gcResult.referencedNodeIds) {
@@ -739,6 +816,12 @@ class GarbageCollector {
739
816
  }
740
817
  else {
741
818
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
819
+ if (this.tombstoneMode && nodeStateTracker.state === exports.UnreferencedState.SweepReady) {
820
+ const nodeType = this.runtime.getNodeType(nodeId);
821
+ if (nodeType === exports.GCNodeType.DataStore || nodeType === exports.GCNodeType.Blob) {
822
+ this.tombstones.push(nodeId);
823
+ }
824
+ }
742
825
  }
743
826
  }
744
827
  }
@@ -757,7 +840,7 @@ class GarbageCollector {
757
840
  }
758
841
  // Find any references that haven't been identified correctly.
759
842
  const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
760
- if (this.writeDataAtRoot && missingExplicitReferences.length > 0) {
843
+ if (missingExplicitReferences.length > 0) {
761
844
  missingExplicitReferences.forEach((missingExplicitReference) => {
762
845
  const event = {
763
846
  eventName: "gcUnknownOutboundReferences",
@@ -968,31 +1051,24 @@ class GarbageCollector {
968
1051
  return;
969
1052
  }
970
1053
  this.loggedUnreferencedEvents.add(uniqueEventId);
971
- const propsToLog = {
972
- id: nodeId,
973
- type: nodeType,
974
- unrefTime: nodeStateTracker.unreferencedTimestampMs,
975
- age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
976
- timeout: nodeStateTracker.state === exports.UnreferencedState.Inactive
1054
+ const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === exports.UnreferencedState.Inactive
977
1055
  ? this.inactiveTimeoutMs
978
- : this.sweepTimeoutMs,
979
- completedGCRuns: this.completedRuns,
980
- lastSummaryTime: this.getLastSummaryTimestampMs(),
981
- externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[containerRuntime_1.RuntimeHeaders.externalRequest],
982
- viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[containerRuntime_1.RuntimeHeaders.viaHandle],
983
- fromId: fromNodeId,
984
- };
1056
+ : this.sweepTimeoutMs, completedGCRuns: this.completedRuns, lastSummaryTime: this.getLastSummaryTimestampMs() }, this.createContainerMetadata), { externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[containerRuntime_1.RuntimeHeaders.externalRequest], viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[containerRuntime_1.RuntimeHeaders.viaHandle], fromId: fromNodeId });
985
1057
  // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
986
1058
  // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
987
1059
  // but it's a good signal nonetheless and we can consume it with a grain of salt.
1060
+ // Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
1061
+ // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
988
1062
  if (this.isSummarizerClient) {
989
1063
  this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
990
1064
  }
991
1065
  else {
992
1066
  // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
993
1067
  // summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
1068
+ // Events generated:
1069
+ // InactiveObject_Loaded, SweepReadyObject_Loaded
994
1070
  if (usageType === "Loaded") {
995
- this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePath ? { value: packagePath.join("/"), tag: telemetry_utils_1.TelemetryDataTag.CodeArtifact } : undefined }));
1071
+ 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)() }));
996
1072
  }
997
1073
  // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
998
1074
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
@@ -1003,6 +1079,11 @@ class GarbageCollector {
1003
1079
  }
1004
1080
  }
1005
1081
  async logUnreferencedEvents(logger) {
1082
+ // Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
1083
+ // summary time they are then logged.
1084
+ // Events generated:
1085
+ // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
1086
+ // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
1006
1087
  for (const eventProps of this.pendingEventsQueue) {
1007
1088
  const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
1008
1089
  /**
@@ -1024,12 +1105,17 @@ class GarbageCollector {
1024
1105
  }
1025
1106
  exports.GarbageCollector = GarbageCollector;
1026
1107
  /**
1027
- * Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
1028
- * Merge the GC state from all such blobs and return the merged GC state.
1108
+ * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1109
+ * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1029
1110
  */
1030
- async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1111
+ async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1031
1112
  let rootGCState = { gcNodes: {} };
1113
+ let tombstones;
1032
1114
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1115
+ if (key === exports.gcTombstoneBlobKey) {
1116
+ tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1117
+ continue;
1118
+ }
1033
1119
  // Skip blobs that do not start with the GC prefix.
1034
1120
  if (!key.startsWith(exports.gcBlobPrefix)) {
1035
1121
  continue;
@@ -1043,7 +1129,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1043
1129
  // Merge the GC state of this blob into the root GC state.
1044
1130
  rootGCState = (0, garbage_collector_1.concatGarbageCollectionStates)(rootGCState, gcState);
1045
1131
  }
1046
- return rootGCState;
1132
+ return { gcState: rootGCState, tombstones };
1047
1133
  }
1048
1134
  function generateSortedGCState(gcState) {
1049
1135
  const sortableArray = Object.entries(gcState.gcNodes);