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

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 (239) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +20 -5
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +57 -15
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +88 -51
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +205 -300
  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 +51 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +40 -32
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +227 -161
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/garbageCollectionConstants.d.ts +19 -0
  28. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  29. package/dist/garbageCollectionConstants.js +34 -0
  30. package/dist/garbageCollectionConstants.js.map +1 -0
  31. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  32. package/dist/gcSweepReadyUsageDetection.js +5 -14
  33. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/opLifecycle/batchManager.d.ts +30 -0
  39. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  40. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  41. package/dist/opLifecycle/batchManager.js.map +1 -0
  42. package/dist/opLifecycle/definitions.d.ts +40 -0
  43. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  44. package/dist/opLifecycle/definitions.js +7 -0
  45. package/dist/opLifecycle/definitions.js.map +1 -0
  46. package/dist/opLifecycle/index.d.ts +12 -0
  47. package/dist/opLifecycle/index.d.ts.map +1 -0
  48. package/dist/opLifecycle/index.js +21 -0
  49. package/dist/opLifecycle/index.js.map +1 -0
  50. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  52. package/dist/opLifecycle/opCompressor.js +53 -0
  53. package/dist/opLifecycle/opCompressor.js.map +1 -0
  54. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  55. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  56. package/dist/opLifecycle/opDecompressor.js +72 -0
  57. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  58. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  60. package/dist/opLifecycle/opSplitter.js +61 -0
  61. package/dist/opLifecycle/opSplitter.js.map +1 -0
  62. package/dist/opLifecycle/outbox.d.ts +47 -0
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  64. package/dist/opLifecycle/outbox.js +153 -0
  65. package/dist/opLifecycle/outbox.js.map +1 -0
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  68. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  69. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/pendingStateManager.d.ts +6 -26
  74. package/dist/pendingStateManager.d.ts.map +1 -1
  75. package/dist/pendingStateManager.js +42 -62
  76. package/dist/pendingStateManager.js.map +1 -1
  77. package/dist/runningSummarizer.d.ts +3 -2
  78. package/dist/runningSummarizer.d.ts.map +1 -1
  79. package/dist/runningSummarizer.js +10 -3
  80. package/dist/runningSummarizer.js.map +1 -1
  81. package/dist/scheduleManager.js.map +1 -1
  82. package/dist/summarizer.js +7 -2
  83. package/dist/summarizer.js.map +1 -1
  84. package/dist/summarizerClientElection.js +1 -1
  85. package/dist/summarizerClientElection.js.map +1 -1
  86. package/dist/summarizerHeuristics.d.ts.map +1 -1
  87. package/dist/summarizerHeuristics.js +0 -3
  88. package/dist/summarizerHeuristics.js.map +1 -1
  89. package/dist/summarizerTypes.d.ts +19 -2
  90. package/dist/summarizerTypes.d.ts.map +1 -1
  91. package/dist/summarizerTypes.js.map +1 -1
  92. package/dist/summaryFormat.d.ts +4 -2
  93. package/dist/summaryFormat.d.ts.map +1 -1
  94. package/dist/summaryFormat.js +2 -2
  95. package/dist/summaryFormat.js.map +1 -1
  96. package/dist/summaryGenerator.d.ts.map +1 -1
  97. package/dist/summaryGenerator.js +3 -2
  98. package/dist/summaryGenerator.js.map +1 -1
  99. package/dist/summaryManager.d.ts.map +1 -1
  100. package/dist/summaryManager.js +10 -6
  101. package/dist/summaryManager.js.map +1 -1
  102. package/garbageCollection.md +27 -22
  103. package/lib/blobManager.d.ts +20 -5
  104. package/lib/blobManager.d.ts.map +1 -1
  105. package/lib/blobManager.js +59 -17
  106. package/lib/blobManager.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +88 -51
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +203 -297
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts.map +1 -1
  112. package/lib/dataStore.js +6 -0
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts +14 -21
  115. package/lib/dataStoreContext.d.ts.map +1 -1
  116. package/lib/dataStoreContext.js +75 -61
  117. package/lib/dataStoreContext.js.map +1 -1
  118. package/lib/dataStoreContexts.js +1 -1
  119. package/lib/dataStoreContexts.js.map +1 -1
  120. package/lib/dataStores.d.ts +11 -10
  121. package/lib/dataStores.d.ts.map +1 -1
  122. package/lib/dataStores.js +53 -22
  123. package/lib/dataStores.js.map +1 -1
  124. package/lib/garbageCollection.d.ts +40 -32
  125. package/lib/garbageCollection.d.ts.map +1 -1
  126. package/lib/garbageCollection.js +220 -154
  127. package/lib/garbageCollection.js.map +1 -1
  128. package/lib/garbageCollectionConstants.d.ts +19 -0
  129. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  130. package/lib/garbageCollectionConstants.js +31 -0
  131. package/lib/garbageCollectionConstants.js.map +1 -0
  132. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  133. package/lib/gcSweepReadyUsageDetection.js +4 -13
  134. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  135. package/lib/index.d.ts +6 -6
  136. package/lib/index.d.ts.map +1 -1
  137. package/lib/index.js +3 -4
  138. package/lib/index.js.map +1 -1
  139. package/lib/opLifecycle/batchManager.d.ts +30 -0
  140. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  141. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  142. package/lib/opLifecycle/batchManager.js.map +1 -0
  143. package/lib/opLifecycle/definitions.d.ts +40 -0
  144. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  145. package/lib/opLifecycle/definitions.js +6 -0
  146. package/lib/opLifecycle/definitions.js.map +1 -0
  147. package/lib/opLifecycle/index.d.ts +12 -0
  148. package/lib/opLifecycle/index.d.ts.map +1 -0
  149. package/lib/opLifecycle/index.js +11 -0
  150. package/lib/opLifecycle/index.js.map +1 -0
  151. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  152. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  153. package/lib/opLifecycle/opCompressor.js +49 -0
  154. package/lib/opLifecycle/opCompressor.js.map +1 -0
  155. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  156. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  157. package/lib/opLifecycle/opDecompressor.js +68 -0
  158. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  159. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  160. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  161. package/lib/opLifecycle/opSplitter.js +57 -0
  162. package/lib/opLifecycle/opSplitter.js.map +1 -0
  163. package/lib/opLifecycle/outbox.d.ts +47 -0
  164. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  165. package/lib/opLifecycle/outbox.js +149 -0
  166. package/lib/opLifecycle/outbox.js.map +1 -0
  167. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  168. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  169. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  170. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  171. package/lib/packageVersion.d.ts +1 -1
  172. package/lib/packageVersion.js +1 -1
  173. package/lib/packageVersion.js.map +1 -1
  174. package/lib/pendingStateManager.d.ts +6 -26
  175. package/lib/pendingStateManager.d.ts.map +1 -1
  176. package/lib/pendingStateManager.js +42 -62
  177. package/lib/pendingStateManager.js.map +1 -1
  178. package/lib/runningSummarizer.d.ts +3 -2
  179. package/lib/runningSummarizer.d.ts.map +1 -1
  180. package/lib/runningSummarizer.js +10 -3
  181. package/lib/runningSummarizer.js.map +1 -1
  182. package/lib/scheduleManager.js.map +1 -1
  183. package/lib/summarizer.js +7 -2
  184. package/lib/summarizer.js.map +1 -1
  185. package/lib/summarizerClientElection.js +1 -1
  186. package/lib/summarizerClientElection.js.map +1 -1
  187. package/lib/summarizerHeuristics.d.ts.map +1 -1
  188. package/lib/summarizerHeuristics.js +0 -3
  189. package/lib/summarizerHeuristics.js.map +1 -1
  190. package/lib/summarizerTypes.d.ts +19 -2
  191. package/lib/summarizerTypes.d.ts.map +1 -1
  192. package/lib/summarizerTypes.js.map +1 -1
  193. package/lib/summaryFormat.d.ts +4 -2
  194. package/lib/summaryFormat.d.ts.map +1 -1
  195. package/lib/summaryFormat.js +1 -1
  196. package/lib/summaryFormat.js.map +1 -1
  197. package/lib/summaryGenerator.d.ts.map +1 -1
  198. package/lib/summaryGenerator.js +3 -2
  199. package/lib/summaryGenerator.js.map +1 -1
  200. package/lib/summaryManager.d.ts.map +1 -1
  201. package/lib/summaryManager.js +10 -6
  202. package/lib/summaryManager.js.map +1 -1
  203. package/package.json +32 -71
  204. package/prettier.config.cjs +8 -0
  205. package/src/blobManager.ts +74 -19
  206. package/src/containerRuntime.ts +286 -369
  207. package/src/dataStore.ts +13 -1
  208. package/src/dataStoreContext.ts +100 -76
  209. package/src/dataStoreContexts.ts +1 -1
  210. package/src/dataStores.ts +61 -22
  211. package/src/garbageCollection.ts +282 -163
  212. package/src/garbageCollectionConstants.ts +35 -0
  213. package/src/gcSweepReadyUsageDetection.ts +3 -11
  214. package/src/index.ts +9 -8
  215. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +42 -28
  216. package/src/opLifecycle/definitions.ts +44 -0
  217. package/src/opLifecycle/index.ts +17 -0
  218. package/src/opLifecycle/opCompressor.ts +64 -0
  219. package/src/opLifecycle/opDecompressor.ts +84 -0
  220. package/src/opLifecycle/opSplitter.ts +78 -0
  221. package/src/opLifecycle/outbox.ts +204 -0
  222. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  223. package/src/packageVersion.ts +1 -1
  224. package/src/pendingStateManager.ts +57 -96
  225. package/src/runningSummarizer.ts +11 -3
  226. package/src/scheduleManager.ts +1 -0
  227. package/src/summarizer.ts +6 -6
  228. package/src/summarizerClientElection.ts +1 -1
  229. package/src/summarizerHeuristics.ts +0 -3
  230. package/src/summarizerTypes.ts +20 -7
  231. package/src/summaryFormat.ts +5 -3
  232. package/src/summaryGenerator.ts +3 -2
  233. package/src/summaryManager.ts +18 -7
  234. package/dist/batchManager.d.ts +0 -37
  235. package/dist/batchManager.d.ts.map +0 -1
  236. package/dist/batchManager.js.map +0 -1
  237. package/lib/batchManager.d.ts +0 -37
  238. package/lib/batchManager.d.ts.map +0 -1
  239. package/lib/batchManager.js.map +0 -1
@@ -18,38 +18,15 @@ 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";
25
+ import { defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcBlobPrefix, gcTestModeKey, gcTombstoneBlobKey, gcTreeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, trackGCStateKey } from "./garbageCollectionConstants";
25
26
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
26
27
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
27
28
  /** This is the current version of garbage collection. */
28
29
  const GCVersion = 1;
29
- // The key for the GC tree in summary.
30
- export const gcTreeKey = "gc";
31
- // They prefix for GC blobs in the GC tree in summary.
32
- export const gcBlobPrefix = "__gc";
33
- // Feature gate key to turn GC on / off.
34
- export const runGCKey = "Fluid.GarbageCollection.RunGC";
35
- // Feature gate key to turn GC sweep on / off.
36
- export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
37
- // Feature gate key to turn GC test mode on / off.
38
- 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
- // Feature gate key to expire a session after a set period of time.
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
- // Feature gate key to write the gc blob as a handle if the data is the same.
46
- export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
47
- // Feature gate key to turn GC sweep log off.
48
- export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
49
- // One day in milliseconds.
50
- export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
51
- export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
52
- export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
53
30
  /** The types of GC nodes in the GC reference graph. */
54
31
  export const GCNodeType = {
55
32
  // Nodes that are for data stores.
@@ -162,10 +139,6 @@ export class UnreferencedStateTracker {
162
139
  export class GarbageCollector {
163
140
  constructor(createParams) {
164
141
  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
142
  /**
170
143
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
171
144
  *
@@ -184,6 +157,7 @@ export class GarbageCollector {
184
157
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
185
158
  // outbound routes from that node.
186
159
  this.newReferencesSinceLastRun = new Map();
160
+ this.tombstones = [];
187
161
  // Map of node ids to their unreferenced state tracker.
188
162
  this.unreferencedNodesState = new Map();
189
163
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -196,6 +170,7 @@ export class GarbageCollector {
196
170
  this.runtime = createParams.runtime;
197
171
  this.isSummarizerClient = createParams.isSummarizerClient;
198
172
  this.gcOptions = createParams.gcOptions;
173
+ this.createContainerMetadata = createParams.createContainerMetadata;
199
174
  this.getNodePackagePath = createParams.getNodePackagePath;
200
175
  this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
201
176
  this.activeConnection = createParams.activeConnection;
@@ -205,6 +180,20 @@ export class GarbageCollector {
205
180
  this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
206
181
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
207
182
  let prevSummaryGCVersion;
183
+ /**
184
+ * Sweep timeout is the time after which unreferenced content can be swept.
185
+ * Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
186
+ *
187
+ * The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
188
+ * The buffer is added to account for any clock skew or other edge cases.
189
+ * We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
190
+ */
191
+ function computeSweepTimeout(sessionExpiryTimeoutMs) {
192
+ const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
193
+ const bufferMs = oneDayMs;
194
+ return sessionExpiryTimeoutMs &&
195
+ (sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
196
+ }
208
197
  /**
209
198
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
210
199
  * 1. Whether running GC mark phase is allowed or not.
@@ -219,6 +208,8 @@ export class GarbageCollector {
219
208
  this.gcEnabled = prevSummaryGCVersion > 0;
220
209
  this.sweepEnabled = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.sweepEnabled) !== null && _a !== void 0 ? _a : false;
221
210
  this.sessionExpiryTimeoutMs = metadata === null || metadata === void 0 ? void 0 : metadata.sessionExpiryTimeoutMs;
211
+ this.sweepTimeoutMs =
212
+ (_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
213
  }
223
214
  else {
224
215
  // Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
@@ -226,36 +217,28 @@ export class GarbageCollector {
226
217
  if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
227
218
  throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
228
219
  }
220
+ // This Test Override only applies for new containers
221
+ const testOverrideSweepTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
229
222
  // For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
230
223
  // flag in GC options to false.
231
224
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
232
225
  // 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;
226
+ // ...unless we're using the TestOverride
227
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
234
228
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
235
229
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
236
- this.sessionExpiryTimeoutMs = (_b = this.gcOptions.sessionExpiryTimeoutMs) !== null && _b !== void 0 ? _b : defaultSessionExpiryDurationMs;
230
+ this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
237
231
  }
232
+ this.sweepTimeoutMs =
233
+ testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
238
234
  }
239
235
  // 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) {
236
+ if (this.sessionExpiryTimeoutMs !== undefined) {
241
237
  // If Test Override config is set, override Session Expiry timeout.
242
238
  const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
243
239
  const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
244
240
  this.sessionExpiryTimer = new Timer(timeoutMs, () => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
245
241
  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
242
  }
260
243
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
261
244
  // latest tracked GC version. For new documents, we will be writing the first summary with the current version.
@@ -278,36 +261,34 @@ export class GarbageCollector {
278
261
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
279
262
  *
280
263
  * 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
281
- *
282
264
  * 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
265
+ * 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
266
+ * the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
267
+ * 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
285
268
  * feature flag.
286
269
  */
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);
270
+ this.shouldRunSweep =
271
+ this.shouldRunGC
272
+ && this.sweepTimeoutMs !== undefined
273
+ && ((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
291
274
  this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
292
275
  // 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;
276
+ 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
277
  // Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
295
278
  if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
296
279
  throw new UsageError("inactive timeout should not be greater than the sweep timeout");
297
280
  }
298
281
  // 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 () => {
282
+ this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
283
+ // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
284
+ this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
285
+ // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
286
+ // contain GC tree and GC is enabled.
287
+ const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
288
+ this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
289
+ // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
290
+ // it involves fetching blobs from storage which is expensive.
291
+ this.baseSnapshotDataP = new LazyPromise(async () => {
311
292
  var _a;
312
293
  if (baseSnapshot === undefined) {
313
294
  return undefined;
@@ -316,13 +297,7 @@ export class GarbageCollector {
316
297
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
317
298
  const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
318
299
  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;
300
+ return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
326
301
  }
327
302
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
328
303
  // consolidate into IGarbageCollectionState format.
@@ -358,7 +333,7 @@ export class GarbageCollector {
358
333
  }
359
334
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
360
335
  // 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;
336
+ return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
362
337
  }
363
338
  catch (error) {
364
339
  const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -367,11 +342,11 @@ export class GarbageCollector {
367
342
  }
368
343
  });
369
344
  /**
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.
345
+ * Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
346
+ * connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
347
+ * GC state and updates their inactive or sweep ready state.
373
348
  */
374
- this.initializeBaseStateP = new LazyPromise(async () => {
349
+ this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
375
350
  const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
376
351
  /**
377
352
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
@@ -389,34 +364,41 @@ export class GarbageCollector {
389
364
  });
390
365
  return;
391
366
  }
392
- const baseState = await baseSummaryStateP;
367
+ const baseSnapshotData = await this.baseSnapshotDataP;
393
368
  /**
394
- * The base state will not be present if the container is loaded from:
369
+ * The base snapshot data will not be present if the container is loaded from:
395
370
  * 1. The first summary created by the detached container.
396
371
  * 2. A summary that was generated with GC disabled.
397
372
  * 3. A summary that was generated before GC even existed.
398
373
  */
399
- if (baseState === undefined) {
374
+ if (baseSnapshotData === undefined) {
400
375
  return;
401
376
  }
402
377
  const gcNodes = {};
403
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
378
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
404
379
  if (nodeData.unreferencedTimestampMs !== undefined) {
405
380
  this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
406
381
  }
407
382
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
408
383
  }
409
384
  this.previousGCDataFromLastRun = { gcNodes };
385
+ // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
386
+ if (this.trackGCState) {
387
+ this.latestSummaryData = {
388
+ serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
389
+ serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
390
+ };
391
+ }
410
392
  });
411
393
  // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
412
394
  // which the caller uses to initialize each node's GC state.
413
395
  this.baseGCDetailsP = new LazyPromise(async () => {
414
- const baseState = await baseSummaryStateP;
415
- if (baseState === undefined) {
396
+ const baseSnapshotData = await this.baseSnapshotDataP;
397
+ if (baseSnapshotData === undefined) {
416
398
  return new Map();
417
399
  }
418
400
  const gcNodes = {};
419
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
401
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
420
402
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
421
403
  }
422
404
  // Run GC on the nodes in the base summary to get the routes used in each node in the container.
@@ -426,7 +408,7 @@ export class GarbageCollector {
426
408
  const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
427
409
  // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
428
410
  // 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)) {
411
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
430
412
  if (nodeData.unreferencedTimestampMs !== undefined) {
431
413
  const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
432
414
  if (dataStoreGCDetails !== undefined) {
@@ -465,12 +447,29 @@ export class GarbageCollector {
465
447
  return this.initialStateNeedsReset ||
466
448
  (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
467
449
  }
468
- get writeDataAtRoot() {
469
- return this._writeDataAtRoot;
470
- }
471
450
  /** Returns a list of all the configurations for garbage collection. */
472
451
  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);
452
+ 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);
453
+ }
454
+ /**
455
+ * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
456
+ * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
457
+ * in use or not.
458
+ */
459
+ async initializeBaseState() {
460
+ const baseSnapshotData = await this.baseSnapshotDataP;
461
+ /**
462
+ * The base snapshot data or tombstone state will not be present if the container is loaded from:
463
+ * 1. The first summary created by the detached container.
464
+ * 2. A summary that was generated with GC disabled.
465
+ * 3. A summary that was generated before GC even existed.
466
+ * 4. A summary that was generated with tombstone feature disabled.
467
+ */
468
+ if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
469
+ return;
470
+ }
471
+ this.tombstones = baseSnapshotData.tombstones;
472
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
474
473
  }
475
474
  /**
476
475
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -480,16 +479,20 @@ export class GarbageCollector {
480
479
  */
481
480
  setConnectionState(connected, clientId) {
482
481
  /**
483
- * For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
482
+ * For all clients, initialize the base state when the container becomes active, i.e., it transitions
484
483
  * to "write" mode. This will ensure that the container's own join op is processed and there is a recent
485
484
  * reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
486
485
  * could affect the GC state will have been processed.
487
486
  *
487
+ * If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
488
+ * InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
489
+ * the receiving summarizer client.
490
+ *
488
491
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
489
492
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
490
493
  */
491
- if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
492
- this.initializeBaseStateP.catch((error) => { });
494
+ if (this.activeConnection() && this.shouldRunGC) {
495
+ this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
493
496
  }
494
497
  }
495
498
  /**
@@ -525,14 +528,14 @@ export class GarbageCollector {
525
528
  const gcData = await this.runtime.getGCData(fullGC);
526
529
  const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
527
530
  const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
528
- event.end(Object.assign({}, gcStats));
531
+ event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
529
532
  this.completedRuns++;
530
533
  return gcStats;
531
534
  }, { end: true, cancel: "error" });
532
535
  }
533
536
  async runPreGCSteps() {
534
- // Ensure that base state has been initialized.
535
- await this.initializeBaseStateP;
537
+ // Ensure that state has been initialized from the base snapshot data.
538
+ await this.initializeGCStateFromBaseSnapshotP;
536
539
  // Let the runtime update its pending state before GC runs.
537
540
  await this.runtime.updateStateBeforeGC();
538
541
  }
@@ -545,14 +548,20 @@ export class GarbageCollector {
545
548
  this.updateStateSinceLastRun(gcData, logger);
546
549
  // Update the current state and update the runtime of all routes or ids that used as per the GC run.
547
550
  this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
548
- this.runtime.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
551
+ this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
549
552
  // Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
550
553
  // delete these objects here instead.
551
554
  this.logSweepEvents(logger, currentReferenceTimestampMs);
552
555
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
553
556
  // involving access to deleted data.
554
557
  if (this.testMode) {
555
- this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
558
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
559
+ }
560
+ else if (this.tombstoneMode) {
561
+ // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
562
+ // scenarios involving access to "deleted" data without actually deleting the data from summaries.
563
+ // Note: we will not tombstone in test mode
564
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
556
565
  }
557
566
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
558
567
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -577,31 +586,69 @@ export class GarbageCollector {
577
586
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
578
587
  };
579
588
  }
580
- const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
589
+ const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
590
+ const serializedTombstones = this.tombstoneMode
591
+ ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
592
+ : undefined;
581
593
  /**
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.
594
+ * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
595
+ * summary, send summary handles for them. Otherwise, send the data in summary blobs.
584
596
  */
585
597
  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
- };
598
+ this.pendingSummaryData = { serializedGCState, serializedTombstones };
599
+ if (trackState && !fullTree && this.latestSummaryData !== undefined) {
600
+ // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
601
+ if (this.latestSummaryData.serializedGCState === serializedGCState
602
+ && this.latestSummaryData.serializedTombstones === serializedTombstones) {
603
+ const stats = mergeStats();
604
+ stats.handleNodeCount++;
605
+ return {
606
+ summary: {
607
+ type: SummaryType.Handle,
608
+ handle: `/${gcTreeKey}`,
609
+ handleType: SummaryType.Tree,
610
+ },
611
+ stats,
612
+ };
613
+ }
614
+ // If either or both of GC state or tombstone state changed, build a GC summary tree.
615
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
601
616
  }
602
617
  }
618
+ // If not tracking GC state, build a GC summary tree without any summary handles.
619
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
620
+ }
621
+ /**
622
+ * Builds the GC summary tree which contains GC state and tombstone state.
623
+ * If trackState is false, both GC state and tombstone state are written as summary blobs.
624
+ * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
625
+ * @param serializedGCState - The GC state serialized as string.
626
+ * @param serializedTombstones - THe tombstone state serialized as string.
627
+ * @param trackState - Whether we are tracking GC state across summaries.
628
+ * @returns the GC summary tree.
629
+ */
630
+ buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
631
+ var _a, _b;
632
+ const gcStateBlobKey = `${gcBlobPrefix}_root`;
603
633
  const builder = new SummaryTreeBuilder();
604
- builder.addBlob(`${gcBlobPrefix}_root`, newSerializedSummaryState);
634
+ // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
635
+ if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
636
+ builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
637
+ }
638
+ else {
639
+ builder.addBlob(gcStateBlobKey, serializedGCState);
640
+ }
641
+ // If there is no tombstone data, return only the GC state.
642
+ if (serializedTombstones === undefined) {
643
+ return builder.getSummaryTree();
644
+ }
645
+ // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
646
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
647
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
648
+ }
649
+ else {
650
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
651
+ }
605
652
  return builder.getSummaryTree();
606
653
  }
607
654
  getMetadata() {
@@ -613,6 +660,7 @@ export class GarbageCollector {
613
660
  gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
614
661
  sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
615
662
  sweepEnabled: this.sweepEnabled,
663
+ sweepTimeoutMs: this.sweepTimeoutMs,
616
664
  };
617
665
  }
618
666
  /**
@@ -636,8 +684,8 @@ export class GarbageCollector {
636
684
  this.latestSummaryGCVersion = this.currentGCVersion;
637
685
  this.initialStateNeedsReset = false;
638
686
  if (this.trackGCState) {
639
- this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
640
- this.pendingSerializedSummaryState = undefined;
687
+ this.latestSummaryData = this.pendingSummaryData;
688
+ this.pendingSummaryData = undefined;
641
689
  }
642
690
  return;
643
691
  }
@@ -651,13 +699,16 @@ export class GarbageCollector {
651
699
  }
652
700
  const gcSnapshotTree = snapshot.trees[gcTreeKey];
653
701
  if (gcSnapshotTree !== undefined && this.trackGCState) {
654
- const latestGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
655
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
702
+ const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
703
+ this.latestSummaryData = {
704
+ serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
705
+ serializedTombstones: JSON.stringify(latestGCData.tombstones),
706
+ };
656
707
  }
657
708
  else {
658
- this.latestSerializedSummaryState = undefined;
709
+ this.latestSummaryData = undefined;
659
710
  }
660
- this.pendingSerializedSummaryState = undefined;
711
+ this.pendingSummaryData = undefined;
661
712
  }
662
713
  /**
663
714
  * Called when a node with the given id is updated. If the node is inactive, log an error.
@@ -712,6 +763,7 @@ export class GarbageCollector {
712
763
  */
713
764
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
714
765
  this.previousGCDataFromLastRun = cloneGCData(gcData);
766
+ this.tombstones = [];
715
767
  this.newReferencesSinceLastRun.clear();
716
768
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
717
769
  for (const nodeId of gcResult.referencedNodeIds) {
@@ -735,13 +787,24 @@ export class GarbageCollector {
735
787
  }
736
788
  else {
737
789
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
790
+ if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
791
+ const nodeType = this.runtime.getNodeType(nodeId);
792
+ if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
793
+ this.tombstones.push(nodeId);
794
+ }
795
+ }
738
796
  }
739
797
  }
740
798
  }
741
799
  /**
742
800
  * Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
743
- * time. It's possible that nodes transition from `unreferenced -> referenced -> unreferenced` between two runs. The
744
- * unreferenced timestamp of such nodes needs to be reset as they may have been accessed when they were referenced.
801
+ * time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
802
+ * updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
803
+ * these objects while there can be in-memory referenced to it:
804
+ * 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
805
+ * added, the object may have been accessed and in-memory reference to it added.
806
+ * 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
807
+ * unreferenced, they could have been accessed and in-memory reference to them added.
745
808
  *
746
809
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
747
810
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
@@ -753,7 +816,7 @@ export class GarbageCollector {
753
816
  }
754
817
  // Find any references that haven't been identified correctly.
755
818
  const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
756
- if (this.writeDataAtRoot && missingExplicitReferences.length > 0) {
819
+ if (missingExplicitReferences.length > 0) {
757
820
  missingExplicitReferences.forEach((missingExplicitReference) => {
758
821
  const event = {
759
822
  eventName: "gcUnknownOutboundReferences",
@@ -774,21 +837,18 @@ export class GarbageCollector {
774
837
  * run, and then add the references since last run.
775
838
  *
776
839
  * Note on why we need to combine the data from previous run, current run and all references in between -
777
- *
778
840
  * 1. We need data from last run because some of its references may have been deleted since then. If those
779
- * references added new outbound references before getting deleted, we need to detect them.
841
+ * references added new outbound references before they were deleted, we need to detect them.
780
842
  *
781
843
  * 2. We need new outbound references since last run because some of them may have been deleted later. If those
782
- * references added new outbound references before getting deleted, we need to detect them.
844
+ * references added new outbound references before they were deleted, we need to detect them.
783
845
  *
784
846
  * 3. We need data from the current run because currently we may not detect when DDSes are referenced:
785
- *
786
- * - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
787
- * which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
788
- *
847
+ * - We don't require DDSes handles to be stored in a referenced DDS.
789
848
  * - A new data store may have "root" DDSes already created and we don't detect them today.
790
849
  */
791
850
  const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
851
+ const newOutboundRoutesSinceLastRun = [];
792
852
  this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
793
853
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
794
854
  gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
@@ -796,19 +856,22 @@ export class GarbageCollector {
796
856
  else {
797
857
  gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
798
858
  }
859
+ newOutboundRoutesSinceLastRun.push(...outboundRoutes);
799
860
  });
800
861
  /**
801
- * Run GC on the above reference graph to find all nodes that are referenced. For each one, if they are
862
+ * Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
863
+ * list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
802
864
  * unreferenced, stop tracking them and remove from unreferenced list.
803
- * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
865
+ * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
866
+ * unreferenced and add unreferenced state.
804
867
  */
805
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
868
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
806
869
  for (const nodeId of gcResult.referencedNodeIds) {
807
870
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
808
871
  if (nodeStateTracker !== undefined) {
809
872
  // Stop tracking so as to clear out any running timers.
810
873
  nodeStateTracker.stopTracking();
811
- // Delete the node as we don't need to track it any more.
874
+ // Delete the unreferenced state as we don't need to track it any more.
812
875
  this.unreferencedNodesState.delete(nodeId);
813
876
  }
814
877
  }
@@ -964,31 +1027,24 @@ export class GarbageCollector {
964
1027
  return;
965
1028
  }
966
1029
  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
1030
+ const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === UnreferencedState.Inactive
973
1031
  ? 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
- };
1032
+ : 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
1033
  // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
982
1034
  // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
983
1035
  // but it's a good signal nonetheless and we can consume it with a grain of salt.
1036
+ // Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
1037
+ // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
984
1038
  if (this.isSummarizerClient) {
985
1039
  this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
986
1040
  }
987
1041
  else {
988
1042
  // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
989
1043
  // summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
1044
+ // Events generated:
1045
+ // InactiveObject_Loaded, SweepReadyObject_Loaded
990
1046
  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() }));
1047
+ this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() }));
992
1048
  }
993
1049
  // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
994
1050
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
@@ -999,6 +1055,11 @@ export class GarbageCollector {
999
1055
  }
1000
1056
  }
1001
1057
  async logUnreferencedEvents(logger) {
1058
+ // Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
1059
+ // summary time they are then logged.
1060
+ // Events generated:
1061
+ // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
1062
+ // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
1002
1063
  for (const eventProps of this.pendingEventsQueue) {
1003
1064
  const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
1004
1065
  /**
@@ -1019,12 +1080,17 @@ export class GarbageCollector {
1019
1080
  }
1020
1081
  }
1021
1082
  /**
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.
1083
+ * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1084
+ * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1024
1085
  */
1025
- async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1086
+ async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1026
1087
  let rootGCState = { gcNodes: {} };
1088
+ let tombstones;
1027
1089
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1090
+ if (key === gcTombstoneBlobKey) {
1091
+ tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1092
+ continue;
1093
+ }
1028
1094
  // Skip blobs that do not start with the GC prefix.
1029
1095
  if (!key.startsWith(gcBlobPrefix)) {
1030
1096
  continue;
@@ -1038,7 +1104,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1038
1104
  // Merge the GC state of this blob into the root GC state.
1039
1105
  rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1040
1106
  }
1041
- return rootGCState;
1107
+ return { gcState: rootGCState, tombstones };
1042
1108
  }
1043
1109
  function generateSortedGCState(gcState) {
1044
1110
  const sortableArray = Object.entries(gcState.gcNodes);