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

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 (340) hide show
  1. package/.eslintrc.js +21 -10
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/batchTracker.d.ts +1 -2
  5. package/dist/batchTracker.d.ts.map +1 -1
  6. package/dist/batchTracker.js +2 -1
  7. package/dist/batchTracker.js.map +1 -1
  8. package/dist/blobManager.d.ts +53 -34
  9. package/dist/blobManager.d.ts.map +1 -1
  10. package/dist/blobManager.js +236 -124
  11. package/dist/blobManager.js.map +1 -1
  12. package/dist/connectionTelemetry.d.ts.map +1 -1
  13. package/dist/connectionTelemetry.js +11 -9
  14. package/dist/connectionTelemetry.js.map +1 -1
  15. package/dist/containerHandleContext.d.ts.map +1 -1
  16. package/dist/containerHandleContext.js +3 -1
  17. package/dist/containerHandleContext.js.map +1 -1
  18. package/dist/containerRuntime.d.ts +95 -46
  19. package/dist/containerRuntime.d.ts.map +1 -1
  20. package/dist/containerRuntime.js +288 -135
  21. package/dist/containerRuntime.js.map +1 -1
  22. package/dist/dataStore.d.ts.map +1 -1
  23. package/dist/dataStore.js +11 -9
  24. package/dist/dataStore.js.map +1 -1
  25. package/dist/dataStoreContext.d.ts +2 -1
  26. package/dist/dataStoreContext.d.ts.map +1 -1
  27. package/dist/dataStoreContext.js +38 -21
  28. package/dist/dataStoreContext.js.map +1 -1
  29. package/dist/dataStoreContexts.d.ts.map +1 -1
  30. package/dist/dataStoreContexts.js +7 -3
  31. package/dist/dataStoreContexts.js.map +1 -1
  32. package/dist/dataStoreRegistry.d.ts.map +1 -1
  33. package/dist/dataStoreRegistry.js +3 -1
  34. package/dist/dataStoreRegistry.js.map +1 -1
  35. package/dist/dataStores.d.ts +12 -9
  36. package/dist/dataStores.d.ts.map +1 -1
  37. package/dist/dataStores.js +68 -46
  38. package/dist/dataStores.js.map +1 -1
  39. package/dist/deltaScheduler.d.ts.map +1 -1
  40. package/dist/deltaScheduler.js +8 -3
  41. package/dist/deltaScheduler.js.map +1 -1
  42. package/dist/garbageCollection.d.ts +50 -26
  43. package/dist/garbageCollection.d.ts.map +1 -1
  44. package/dist/garbageCollection.js +348 -196
  45. package/dist/garbageCollection.js.map +1 -1
  46. package/dist/garbageCollectionConstants.d.ts +7 -3
  47. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  48. package/dist/garbageCollectionConstants.js +10 -8
  49. package/dist/garbageCollectionConstants.js.map +1 -1
  50. package/dist/garbageCollectionHelpers.d.ts +15 -0
  51. package/dist/garbageCollectionHelpers.d.ts.map +1 -0
  52. package/dist/garbageCollectionHelpers.js +27 -0
  53. package/dist/garbageCollectionHelpers.js.map +1 -0
  54. package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
  55. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  56. package/dist/gcSweepReadyUsageDetection.js +14 -10
  57. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  58. package/dist/index.d.ts +3 -4
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -5
  61. package/dist/index.js.map +1 -1
  62. package/dist/opLifecycle/batchManager.d.ts +13 -1
  63. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  64. package/dist/opLifecycle/batchManager.js +48 -7
  65. package/dist/opLifecycle/batchManager.js.map +1 -1
  66. package/dist/opLifecycle/definitions.d.ts +25 -1
  67. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  68. package/dist/opLifecycle/definitions.js.map +1 -1
  69. package/dist/opLifecycle/index.d.ts +2 -2
  70. package/dist/opLifecycle/index.d.ts.map +1 -1
  71. package/dist/opLifecycle/index.js +2 -1
  72. package/dist/opLifecycle/index.js.map +1 -1
  73. package/dist/opLifecycle/opCompressor.d.ts +1 -1
  74. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  75. package/dist/opLifecycle/opCompressor.js +24 -10
  76. package/dist/opLifecycle/opCompressor.js.map +1 -1
  77. package/dist/opLifecycle/opDecompressor.d.ts +2 -1
  78. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  79. package/dist/opLifecycle/opDecompressor.js +33 -17
  80. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  81. package/dist/opLifecycle/opSplitter.d.ts +34 -2
  82. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  83. package/dist/opLifecycle/opSplitter.js +117 -5
  84. package/dist/opLifecycle/opSplitter.js.map +1 -1
  85. package/dist/opLifecycle/outbox.d.ts +5 -0
  86. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  87. package/dist/opLifecycle/outbox.js +38 -27
  88. package/dist/opLifecycle/outbox.js.map +1 -1
  89. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  90. package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
  91. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  92. package/dist/opProperties.d.ts.map +1 -1
  93. package/dist/opProperties.js +1 -3
  94. package/dist/opProperties.js.map +1 -1
  95. package/dist/orderedClientElection.d.ts.map +1 -1
  96. package/dist/orderedClientElection.js +10 -4
  97. package/dist/orderedClientElection.js.map +1 -1
  98. package/dist/packageVersion.d.ts +1 -1
  99. package/dist/packageVersion.js +1 -1
  100. package/dist/packageVersion.js.map +1 -1
  101. package/dist/pendingStateManager.d.ts +4 -13
  102. package/dist/pendingStateManager.d.ts.map +1 -1
  103. package/dist/pendingStateManager.js +134 -161
  104. package/dist/pendingStateManager.js.map +1 -1
  105. package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
  106. package/dist/runWhileConnectedCoordinator.js.map +1 -1
  107. package/dist/runningSummarizer.d.ts.map +1 -1
  108. package/dist/runningSummarizer.js +34 -22
  109. package/dist/runningSummarizer.js.map +1 -1
  110. package/dist/scheduleManager.d.ts +0 -1
  111. package/dist/scheduleManager.d.ts.map +1 -1
  112. package/dist/scheduleManager.js +11 -21
  113. package/dist/scheduleManager.js.map +1 -1
  114. package/dist/serializedSnapshotStorage.d.ts.map +1 -1
  115. package/dist/serializedSnapshotStorage.js +3 -1
  116. package/dist/serializedSnapshotStorage.js.map +1 -1
  117. package/dist/summarizer.d.ts +2 -3
  118. package/dist/summarizer.d.ts.map +1 -1
  119. package/dist/summarizer.js +39 -18
  120. package/dist/summarizer.js.map +1 -1
  121. package/dist/summarizerClientElection.d.ts +1 -2
  122. package/dist/summarizerClientElection.d.ts.map +1 -1
  123. package/dist/summarizerClientElection.js +3 -30
  124. package/dist/summarizerClientElection.js.map +1 -1
  125. package/dist/summarizerHandle.d.ts.map +1 -1
  126. package/dist/summarizerHandle.js.map +1 -1
  127. package/dist/summarizerHeuristics.d.ts.map +1 -1
  128. package/dist/summarizerHeuristics.js +6 -9
  129. package/dist/summarizerHeuristics.js.map +1 -1
  130. package/dist/summarizerTypes.d.ts +22 -25
  131. package/dist/summarizerTypes.d.ts.map +1 -1
  132. package/dist/summarizerTypes.js.map +1 -1
  133. package/dist/summaryCollection.d.ts.map +1 -1
  134. package/dist/summaryCollection.js +18 -8
  135. package/dist/summaryCollection.js.map +1 -1
  136. package/dist/summaryFormat.d.ts.map +1 -1
  137. package/dist/summaryFormat.js +18 -11
  138. package/dist/summaryFormat.js.map +1 -1
  139. package/dist/summaryGenerator.d.ts.map +1 -1
  140. package/dist/summaryGenerator.js +32 -14
  141. package/dist/summaryGenerator.js.map +1 -1
  142. package/dist/summaryManager.d.ts.map +1 -1
  143. package/dist/summaryManager.js +21 -9
  144. package/dist/summaryManager.js.map +1 -1
  145. package/dist/throttler.d.ts +2 -2
  146. package/dist/throttler.d.ts.map +1 -1
  147. package/dist/throttler.js +4 -4
  148. package/dist/throttler.js.map +1 -1
  149. package/garbageCollection.md +15 -2
  150. package/lib/batchTracker.d.ts +1 -2
  151. package/lib/batchTracker.d.ts.map +1 -1
  152. package/lib/batchTracker.js +2 -1
  153. package/lib/batchTracker.js.map +1 -1
  154. package/lib/blobManager.d.ts +53 -34
  155. package/lib/blobManager.d.ts.map +1 -1
  156. package/lib/blobManager.js +239 -127
  157. package/lib/blobManager.js.map +1 -1
  158. package/lib/connectionTelemetry.d.ts.map +1 -1
  159. package/lib/connectionTelemetry.js +11 -9
  160. package/lib/connectionTelemetry.js.map +1 -1
  161. package/lib/containerHandleContext.d.ts.map +1 -1
  162. package/lib/containerHandleContext.js +3 -1
  163. package/lib/containerHandleContext.js.map +1 -1
  164. package/lib/containerRuntime.d.ts +95 -46
  165. package/lib/containerRuntime.d.ts.map +1 -1
  166. package/lib/containerRuntime.js +291 -138
  167. package/lib/containerRuntime.js.map +1 -1
  168. package/lib/dataStore.d.ts.map +1 -1
  169. package/lib/dataStore.js +11 -9
  170. package/lib/dataStore.js.map +1 -1
  171. package/lib/dataStoreContext.d.ts +2 -1
  172. package/lib/dataStoreContext.d.ts.map +1 -1
  173. package/lib/dataStoreContext.js +40 -23
  174. package/lib/dataStoreContext.js.map +1 -1
  175. package/lib/dataStoreContexts.d.ts.map +1 -1
  176. package/lib/dataStoreContexts.js +7 -3
  177. package/lib/dataStoreContexts.js.map +1 -1
  178. package/lib/dataStoreRegistry.d.ts.map +1 -1
  179. package/lib/dataStoreRegistry.js +3 -1
  180. package/lib/dataStoreRegistry.js.map +1 -1
  181. package/lib/dataStores.d.ts +12 -9
  182. package/lib/dataStores.d.ts.map +1 -1
  183. package/lib/dataStores.js +74 -52
  184. package/lib/dataStores.js.map +1 -1
  185. package/lib/deltaScheduler.d.ts.map +1 -1
  186. package/lib/deltaScheduler.js +9 -4
  187. package/lib/deltaScheduler.js.map +1 -1
  188. package/lib/garbageCollection.d.ts +50 -26
  189. package/lib/garbageCollection.d.ts.map +1 -1
  190. package/lib/garbageCollection.js +347 -195
  191. package/lib/garbageCollection.js.map +1 -1
  192. package/lib/garbageCollectionConstants.d.ts +7 -3
  193. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  194. package/lib/garbageCollectionConstants.js +9 -7
  195. package/lib/garbageCollectionConstants.js.map +1 -1
  196. package/lib/garbageCollectionHelpers.d.ts +15 -0
  197. package/lib/garbageCollectionHelpers.d.ts.map +1 -0
  198. package/lib/garbageCollectionHelpers.js +23 -0
  199. package/lib/garbageCollectionHelpers.js.map +1 -0
  200. package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
  201. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  202. package/lib/gcSweepReadyUsageDetection.js +14 -10
  203. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  204. package/lib/index.d.ts +3 -4
  205. package/lib/index.d.ts.map +1 -1
  206. package/lib/index.js +2 -3
  207. package/lib/index.js.map +1 -1
  208. package/lib/opLifecycle/batchManager.d.ts +13 -1
  209. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  210. package/lib/opLifecycle/batchManager.js +48 -7
  211. package/lib/opLifecycle/batchManager.js.map +1 -1
  212. package/lib/opLifecycle/definitions.d.ts +25 -1
  213. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  214. package/lib/opLifecycle/definitions.js.map +1 -1
  215. package/lib/opLifecycle/index.d.ts +2 -2
  216. package/lib/opLifecycle/index.d.ts.map +1 -1
  217. package/lib/opLifecycle/index.js +1 -1
  218. package/lib/opLifecycle/index.js.map +1 -1
  219. package/lib/opLifecycle/opCompressor.d.ts +1 -1
  220. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  221. package/lib/opLifecycle/opCompressor.js +24 -10
  222. package/lib/opLifecycle/opCompressor.js.map +1 -1
  223. package/lib/opLifecycle/opDecompressor.d.ts +2 -1
  224. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  225. package/lib/opLifecycle/opDecompressor.js +33 -17
  226. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  227. package/lib/opLifecycle/opSplitter.d.ts +34 -2
  228. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  229. package/lib/opLifecycle/opSplitter.js +116 -5
  230. package/lib/opLifecycle/opSplitter.js.map +1 -1
  231. package/lib/opLifecycle/outbox.d.ts +5 -0
  232. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  233. package/lib/opLifecycle/outbox.js +38 -27
  234. package/lib/opLifecycle/outbox.js.map +1 -1
  235. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  236. package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
  237. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  238. package/lib/opProperties.d.ts.map +1 -1
  239. package/lib/opProperties.js +1 -3
  240. package/lib/opProperties.js.map +1 -1
  241. package/lib/orderedClientElection.d.ts.map +1 -1
  242. package/lib/orderedClientElection.js +10 -4
  243. package/lib/orderedClientElection.js.map +1 -1
  244. package/lib/packageVersion.d.ts +1 -1
  245. package/lib/packageVersion.js +1 -1
  246. package/lib/packageVersion.js.map +1 -1
  247. package/lib/pendingStateManager.d.ts +4 -13
  248. package/lib/pendingStateManager.d.ts.map +1 -1
  249. package/lib/pendingStateManager.js +134 -161
  250. package/lib/pendingStateManager.js.map +1 -1
  251. package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
  252. package/lib/runWhileConnectedCoordinator.js.map +1 -1
  253. package/lib/runningSummarizer.d.ts.map +1 -1
  254. package/lib/runningSummarizer.js +35 -23
  255. package/lib/runningSummarizer.js.map +1 -1
  256. package/lib/scheduleManager.d.ts +0 -1
  257. package/lib/scheduleManager.d.ts.map +1 -1
  258. package/lib/scheduleManager.js +11 -21
  259. package/lib/scheduleManager.js.map +1 -1
  260. package/lib/serializedSnapshotStorage.d.ts.map +1 -1
  261. package/lib/serializedSnapshotStorage.js +3 -1
  262. package/lib/serializedSnapshotStorage.js.map +1 -1
  263. package/lib/summarizer.d.ts +2 -3
  264. package/lib/summarizer.d.ts.map +1 -1
  265. package/lib/summarizer.js +39 -18
  266. package/lib/summarizer.js.map +1 -1
  267. package/lib/summarizerClientElection.d.ts +1 -2
  268. package/lib/summarizerClientElection.d.ts.map +1 -1
  269. package/lib/summarizerClientElection.js +3 -30
  270. package/lib/summarizerClientElection.js.map +1 -1
  271. package/lib/summarizerHandle.d.ts.map +1 -1
  272. package/lib/summarizerHandle.js.map +1 -1
  273. package/lib/summarizerHeuristics.d.ts.map +1 -1
  274. package/lib/summarizerHeuristics.js +6 -9
  275. package/lib/summarizerHeuristics.js.map +1 -1
  276. package/lib/summarizerTypes.d.ts +22 -25
  277. package/lib/summarizerTypes.d.ts.map +1 -1
  278. package/lib/summarizerTypes.js.map +1 -1
  279. package/lib/summaryCollection.d.ts.map +1 -1
  280. package/lib/summaryCollection.js +18 -8
  281. package/lib/summaryCollection.js.map +1 -1
  282. package/lib/summaryFormat.d.ts.map +1 -1
  283. package/lib/summaryFormat.js +20 -13
  284. package/lib/summaryFormat.js.map +1 -1
  285. package/lib/summaryGenerator.d.ts.map +1 -1
  286. package/lib/summaryGenerator.js +32 -14
  287. package/lib/summaryGenerator.js.map +1 -1
  288. package/lib/summaryManager.d.ts.map +1 -1
  289. package/lib/summaryManager.js +21 -9
  290. package/lib/summaryManager.js.map +1 -1
  291. package/lib/throttler.d.ts +2 -2
  292. package/lib/throttler.d.ts.map +1 -1
  293. package/lib/throttler.js +4 -4
  294. package/lib/throttler.js.map +1 -1
  295. package/package.json +27 -24
  296. package/prettier.config.cjs +1 -1
  297. package/src/batchTracker.ts +55 -50
  298. package/src/blobManager.ts +799 -593
  299. package/src/connectionTelemetry.ts +280 -249
  300. package/src/containerHandleContext.ts +27 -29
  301. package/src/containerRuntime.ts +3123 -2793
  302. package/src/dataStore.ts +172 -159
  303. package/src/dataStoreContext.ts +1048 -991
  304. package/src/dataStoreContexts.ts +178 -161
  305. package/src/dataStoreRegistry.ts +25 -20
  306. package/src/dataStores.ts +784 -711
  307. package/src/deltaScheduler.ts +158 -150
  308. package/src/garbageCollection.ts +1795 -1546
  309. package/src/garbageCollectionConstants.ts +10 -7
  310. package/src/garbageCollectionHelpers.ts +37 -0
  311. package/src/gcSweepReadyUsageDetection.ts +89 -83
  312. package/src/index.ts +67 -69
  313. package/src/opLifecycle/batchManager.ts +148 -86
  314. package/src/opLifecycle/definitions.ts +45 -19
  315. package/src/opLifecycle/index.ts +6 -5
  316. package/src/opLifecycle/opCompressor.ts +57 -39
  317. package/src/opLifecycle/opDecompressor.ts +104 -64
  318. package/src/opLifecycle/opSplitter.ts +226 -66
  319. package/src/opLifecycle/outbox.ts +206 -182
  320. package/src/opLifecycle/remoteMessageProcessor.ts +63 -47
  321. package/src/opProperties.ts +11 -9
  322. package/src/orderedClientElection.ts +489 -457
  323. package/src/packageVersion.ts +1 -1
  324. package/src/pendingStateManager.ts +379 -381
  325. package/src/runWhileConnectedCoordinator.ts +78 -71
  326. package/src/runningSummarizer.ts +619 -582
  327. package/src/scheduleManager.ts +299 -280
  328. package/src/serializedSnapshotStorage.ts +116 -111
  329. package/src/summarizer.ts +417 -381
  330. package/src/summarizerClientElection.ts +107 -129
  331. package/src/summarizerHandle.ts +11 -9
  332. package/src/summarizerHeuristics.ts +183 -186
  333. package/src/summarizerTypes.ts +344 -333
  334. package/src/summaryCollection.ts +378 -349
  335. package/src/summaryFormat.ts +146 -127
  336. package/src/summaryGenerator.ts +464 -406
  337. package/src/summaryManager.ts +377 -348
  338. package/src/throttler.ts +131 -122
  339. package/tsconfig.esnext.json +6 -6
  340. package/tsconfig.json +9 -13
@@ -14,19 +14,18 @@ var __rest = (this && this.__rest) || function (s, e) {
14
14
  return t;
15
15
  };
16
16
  import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
17
- import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
18
- import { cloneGCData, concatGarbageCollectionStates, concatGarbageCollectionData, runGarbageCollection, unpackChildNodesGCDetails, } from "@fluidframework/garbage-collector";
17
+ import { ClientSessionExpiredError, DataProcessingError, UsageError, } from "@fluidframework/container-utils";
18
+ import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot, runGarbageCollection, trimLeadingSlashes, } from "@fluidframework/garbage-collector";
19
19
  import { SummaryType } from "@fluidframework/protocol-definitions";
20
- import { gcBlobKey, } from "@fluidframework/runtime-definitions";
20
+ import { gcTreeKey, gcBlobPrefix, gcTombstoneBlobKey, gcDeletedBlobKey, } from "@fluidframework/runtime-definitions";
21
21
  import { mergeStats, packagePathToTelemetryProperty, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
22
22
  import { ChildLogger, generateStack, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
23
23
  import { RuntimeHeaders } from "./containerRuntime";
24
24
  import { getSummaryForDatastores } from "./dataStores";
25
- import { defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcBlobPrefix, gcTestModeKey, gcTombstoneBlobKey, gcTreeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, trackGCStateKey } from "./garbageCollectionConstants";
25
+ import { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, trackGCStateKey, } from "./garbageCollectionConstants";
26
+ import { sendGCUnexpectedUsageEvent } from "./garbageCollectionHelpers";
26
27
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
27
28
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
28
- /** This is the current version of garbage collection. */
29
- const GCVersion = 1;
30
29
  /** The types of GC nodes in the GC reference graph. */
31
30
  export const GCNodeType = {
32
31
  // Nodes that are for data stores.
@@ -139,25 +138,13 @@ export class UnreferencedStateTracker {
139
138
  export class GarbageCollector {
140
139
  constructor(createParams) {
141
140
  var _a, _b, _c, _d, _e, _f, _g, _h;
142
- /**
143
- * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
144
- *
145
- * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
146
- * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
147
- *
148
- * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
149
- * a document and the first time GC is enabled after is was disabled before.
150
- *
151
- * Note that the state needs reset only for the very first time summary is generated by this client. After that, the
152
- * state will be up-to-date and this flag will be reset.
153
- */
154
- this.initialStateNeedsReset = false;
155
- // The current GC version that this container is running.
156
- this.currentGCVersion = GCVersion;
157
141
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
158
142
  // outbound routes from that node.
159
143
  this.newReferencesSinceLastRun = new Map();
144
+ // A list of nodes that have been tombstoned.
160
145
  this.tombstones = [];
146
+ // A list of nodes that have been deleted during sweep phase.
147
+ this.deletedNodes = new Set();
161
148
  // Map of node ids to their unreferenced state tracker.
162
149
  this.unreferencedNodesState = new Map();
163
150
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -177,7 +164,14 @@ export class GarbageCollector {
177
164
  const baseSnapshot = createParams.baseSnapshot;
178
165
  const metadata = createParams.metadata;
179
166
  const readAndParseBlob = createParams.readAndParseBlob;
180
- this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
167
+ this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", {
168
+ all: { completedGCRuns: () => this.completedRuns },
169
+ }));
170
+ // If version upgrade is not enabled, fall back to the stable GC version.
171
+ this.currentGCVersion =
172
+ this.mc.config.getBoolean(gcVersionUpgradeToV2Key) === true
173
+ ? currentGCVersion
174
+ : stableGCVersion;
181
175
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
182
176
  let prevSummaryGCVersion;
183
177
  /**
@@ -191,8 +185,8 @@ export class GarbageCollector {
191
185
  function computeSweepTimeout(sessionExpiryTimeoutMs) {
192
186
  const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
193
187
  const bufferMs = oneDayMs;
194
- return sessionExpiryTimeoutMs &&
195
- (sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
188
+ return (sessionExpiryTimeoutMs &&
189
+ sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
196
190
  }
197
191
  /**
198
192
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
@@ -223,11 +217,11 @@ export class GarbageCollector {
223
217
  // flag in GC options to false.
224
218
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
225
219
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
226
- // ...unless we're using the TestOverride
227
- this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
220
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
228
221
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
229
222
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
230
- this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
223
+ this.sessionExpiryTimeoutMs =
224
+ (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
231
225
  }
232
226
  this.sweepTimeoutMs =
233
227
  testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
@@ -237,7 +231,9 @@ export class GarbageCollector {
237
231
  // If Test Override config is set, override Session Expiry timeout.
238
232
  const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
239
233
  const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
240
- this.sessionExpiryTimer = new Timer(timeoutMs, () => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
234
+ this.sessionExpiryTimer = new Timer(timeoutMs, () => {
235
+ this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
236
+ });
241
237
  this.sessionExpiryTimer.start();
242
238
  }
243
239
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
@@ -252,11 +248,12 @@ export class GarbageCollector {
252
248
  *
253
249
  * These conditions can be overridden via runGCKey feature flag.
254
250
  */
255
- this.shouldRunGC = (_d = this.mc.config.getBoolean(runGCKey)) !== null && _d !== void 0 ? _d : (
256
- // GC must be enabled for the document.
257
- this.gcEnabled
258
- // GC must not be disabled via GC options.
259
- && !this.gcOptions.disableGC);
251
+ this.shouldRunGC =
252
+ (_d = this.mc.config.getBoolean(runGCKey)) !== null && _d !== void 0 ? _d :
253
+ // GC must be enabled for the document.
254
+ (this.gcEnabled &&
255
+ // GC must not be disabled via GC options.
256
+ !this.gcOptions.disableGC);
260
257
  /**
261
258
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
262
259
  *
@@ -268,24 +265,26 @@ export class GarbageCollector {
268
265
  * feature flag.
269
266
  */
270
267
  this.shouldRunSweep =
271
- this.shouldRunGC
272
- && this.sweepTimeoutMs !== undefined
273
- && ((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
268
+ this.shouldRunGC &&
269
+ this.sweepTimeoutMs !== undefined &&
270
+ ((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
274
271
  this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
275
272
  // Override inactive timeout if test config or gc options to override it is set.
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;
273
+ this.inactiveTimeoutMs =
274
+ (_g = (_f = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _f !== void 0 ? _f : this.gcOptions.inactiveTimeoutMs) !== null && _g !== void 0 ? _g : defaultInactiveTimeoutMs;
277
275
  // Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
278
276
  if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
279
277
  throw new UsageError("inactive timeout should not be greater than the sweep timeout");
280
278
  }
281
279
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
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;
280
+ this.testMode =
281
+ (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
282
+ // Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
283
+ // via feature flags.
284
+ this.tombstoneMode =
285
+ !this.shouldRunSweep && this.mc.config.getBoolean(disableTombstoneKey) !== true;
286
+ // If GC ran in the container that generated the base snapshot, it will have a GC tree.
287
+ this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
289
288
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
290
289
  // it involves fetching blobs from storage which is expensive.
291
290
  this.baseSnapshotDataP = new LazyPromise(async () => {
@@ -302,11 +301,13 @@ export class GarbageCollector {
302
301
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
303
302
  // consolidate into IGarbageCollectionState format.
304
303
  // Add a node for the root node that is not present in older snapshot format.
305
- const gcState = { gcNodes: { "/": { outboundRoutes: [] } } };
304
+ const gcState = {
305
+ gcNodes: { "/": { outboundRoutes: [] } },
306
+ };
306
307
  const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
307
308
  assert(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
308
309
  for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
309
- const blobId = dsSnapshotTree.blobs[gcBlobKey];
310
+ const blobId = dsSnapshotTree.blobs[gcTreeKey];
310
311
  if (blobId === undefined) {
311
312
  continue;
312
313
  }
@@ -326,14 +327,19 @@ export class GarbageCollector {
326
327
  // Prefix the data store id to the GC node ids to make them relative to the root from being
327
328
  // relative to the data store. Similar to how its done in DataStore::getGCData.
328
329
  const rootId = id === "/" ? dsRootId : `${dsRootId}${id}`;
329
- gcState.gcNodes[rootId] = { outboundRoutes: Array.from(outboundRoutes) };
330
+ gcState.gcNodes[rootId] = {
331
+ outboundRoutes: Array.from(outboundRoutes),
332
+ };
330
333
  }
331
334
  assert(gcState.gcNodes[dsRootId] !== undefined, 0x2a9 /* GC nodes for data store not in GC blob */);
332
- gcState.gcNodes[dsRootId].unreferencedTimestampMs = gcSummaryDetails.unrefTimestamp;
335
+ gcState.gcNodes[dsRootId].unreferencedTimestampMs =
336
+ gcSummaryDetails.unrefTimestamp;
333
337
  }
334
338
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
335
339
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
336
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
340
+ return Object.keys(gcState.gcNodes).length === 1
341
+ ? undefined
342
+ : { gcState, tombstones: undefined, deletedNodes: undefined };
337
343
  }
338
344
  catch (error) {
339
345
  const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -347,7 +353,6 @@ export class GarbageCollector {
347
353
  * GC state and updates their inactive or sweep ready state.
348
354
  */
349
355
  this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
350
- const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
351
356
  /**
352
357
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
353
358
  * how long objects have been unreferenced and if they can be deleted.
@@ -356,6 +361,7 @@ export class GarbageCollector {
356
361
  * for this container and it is in read mode. In this scenario, there is no point in running GC anyway
357
362
  * because references in the container do not change without any ops, i.e., there is nothing to collect.
358
363
  */
364
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
359
365
  if (currentReferenceTimestampMs === undefined) {
360
366
  // Log an event so we can evaluate how often we run into this scenario.
361
367
  this.mc.logger.sendErrorEvent({
@@ -364,38 +370,24 @@ export class GarbageCollector {
364
370
  });
365
371
  return;
366
372
  }
367
- const baseSnapshotData = await this.baseSnapshotDataP;
368
373
  /**
369
374
  * The base snapshot data will not be present if the container is loaded from:
370
375
  * 1. The first summary created by the detached container.
371
376
  * 2. A summary that was generated with GC disabled.
372
377
  * 3. A summary that was generated before GC even existed.
373
378
  */
379
+ const baseSnapshotData = await this.baseSnapshotDataP;
374
380
  if (baseSnapshotData === undefined) {
375
381
  return;
376
382
  }
377
- const gcNodes = {};
378
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
379
- if (nodeData.unreferencedTimestampMs !== undefined) {
380
- this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
381
- }
382
- gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
383
- }
384
- this.previousGCDataFromLastRun = { gcNodes };
385
- // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
386
- if (this.trackGCState) {
387
- this.latestSummaryData = {
388
- serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
389
- serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
390
- };
391
- }
383
+ this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
392
384
  });
393
- // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
394
- // which the caller uses to initialize each node's GC state.
385
+ // Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
386
+ // used to initialize the GC state of all the nodes in the container.
395
387
  this.baseGCDetailsP = new LazyPromise(async () => {
396
388
  const baseSnapshotData = await this.baseSnapshotDataP;
397
389
  if (baseSnapshotData === undefined) {
398
- return new Map();
390
+ return {};
399
391
  }
400
392
  const gcNodes = {};
401
393
  for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
@@ -405,18 +397,7 @@ export class GarbageCollector {
405
397
  // This is an optimization for space (vs performance) wherein we don't need to store the used routes of
406
398
  // each node in the summary.
407
399
  const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
408
- const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
409
- // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
410
- // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
411
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
412
- if (nodeData.unreferencedTimestampMs !== undefined) {
413
- const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
414
- if (dataStoreGCDetails !== undefined) {
415
- dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
416
- }
417
- }
418
- }
419
- return baseGCDetailsMap;
400
+ return { gcData: { gcNodes }, usedRoutes };
420
401
  });
421
402
  // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
422
403
  // summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
@@ -437,39 +418,143 @@ export class GarbageCollector {
437
418
  *
438
419
  * 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
439
420
  *
440
- * 3. The GC version in the latest summary is different from the current GC version. This can happen if:
421
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
422
+ *
423
+ * 4. The GC version in the latest summary is different from the current GC version. This can happen if:
441
424
  *
442
- * 3.1. The summary this client loaded with has data from a different GC version.
425
+ * 4.1. The summary this client loaded with has data from a different GC version.
443
426
  *
444
- * 3.2. This client's latest summary was updated from a snapshot that has a different GC version.
427
+ * 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
445
428
  */
446
429
  get summaryStateNeedsReset() {
447
- return this.initialStateNeedsReset ||
448
- (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
430
+ return (this.gcStateNeedsReset ||
431
+ (this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion));
432
+ }
433
+ /**
434
+ * Tells whether the GC state needs to be reset. This can happen under 3 conditions:
435
+ *
436
+ * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
437
+ * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
438
+ *
439
+ * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
440
+ * a document and the first time GC is enabled after is was disabled before.
441
+ *
442
+ * 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
443
+ *
444
+ * Note that the state will be reset only once for the first summary generated after this returns true. After that,
445
+ * this will return false.
446
+ */
447
+ get gcStateNeedsReset() {
448
+ return this.wasGCRunInLatestSummary !== this.shouldRunGC;
449
449
  }
450
450
  /** Returns a list of all the configurations for garbage collection. */
451
451
  get configs() {
452
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
453
  }
454
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.
455
+ * Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
456
+ * during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
458
457
  */
459
458
  async initializeBaseState() {
460
459
  const baseSnapshotData = await this.baseSnapshotDataP;
461
460
  /**
462
- * The base snapshot data or tombstone state will not be present if the container is loaded from:
461
+ * The base snapshot data will not be present if the container is loaded from:
463
462
  * 1. The first summary created by the detached container.
464
463
  * 2. A summary that was generated with GC disabled.
465
464
  * 3. A summary that was generated before GC even existed.
466
- * 4. A summary that was generated with tombstone feature disabled.
467
465
  */
468
- if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
466
+ if (baseSnapshotData === undefined) {
469
467
  return;
470
468
  }
471
- this.tombstones = baseSnapshotData.tombstones;
472
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
469
+ // Initialize the deleted nodes from the snapshot. This is done irrespective of whether sweep is enabled or not
470
+ // to identify deleted nodes' usage.
471
+ if (baseSnapshotData.deletedNodes !== undefined) {
472
+ this.deletedNodes = new Set(baseSnapshotData.deletedNodes);
473
+ }
474
+ // If running in tombstone mode, initialize the tombstone state from the snapshot. Also, notify the runtime of
475
+ // tombstone routes.
476
+ if (this.tombstoneMode && baseSnapshotData.tombstones !== undefined) {
477
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
478
+ this.runtime.updateTombstonedRoutes(this.tombstones);
479
+ }
480
+ }
481
+ /**
482
+ * Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
483
+ * All current tracking is reset and updated from the data in the snapshot.
484
+ * @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
485
+ * is reset.
486
+ * @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
487
+ * timestamp.
488
+ */
489
+ updateStateFromSnapshotData(snapshotData, currentReferenceTimestampMs) {
490
+ /**
491
+ * Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
492
+ * snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
493
+ * its refreshing state from a summary that happened at seq#900. In this case, there may be references between
494
+ * seq#901 and seq#1000 that we don't want to reset.
495
+ * Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
496
+ * references here. This should be fine because, in the worst case, we may end up updating the unreferenced
497
+ * timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
498
+ * scenarios, so it should be okay.
499
+ */
500
+ // Clear all existing unreferenced state tracking.
501
+ for (const [, nodeStateTracker] of this.unreferencedNodesState) {
502
+ nodeStateTracker.stopTracking();
503
+ }
504
+ this.unreferencedNodesState.clear();
505
+ // If running sweep, the tombstone state represents the list of nodes that have been deleted during sweep.
506
+ // If running in tombstone mode, the tombstone state represents the list of nodes that have been marked as
507
+ // tombstones.
508
+ // If this call is because we are refreshing from a snapshot due to an ack, it is likely that the GC state
509
+ // in the snapshot is newer than this client's. And so, the deleted / tombstone nodes need to be updated.
510
+ if (this.shouldRunSweep) {
511
+ const snapshotDeletedNodes = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones)
512
+ ? new Set(snapshotData.tombstones)
513
+ : undefined;
514
+ // If the snapshot contains deleted nodes that are not yet deleted by this client, ask the runtime to
515
+ // delete them.
516
+ if (snapshotDeletedNodes !== undefined) {
517
+ const newDeletedNodes = [];
518
+ for (const nodeId of snapshotDeletedNodes) {
519
+ if (!this.deletedNodes.has(nodeId)) {
520
+ newDeletedNodes.push(nodeId);
521
+ }
522
+ }
523
+ if (newDeletedNodes.length > 0) {
524
+ // Call container runtime to delete these nodes and add deleted nodes to this.deletedNodes.
525
+ }
526
+ }
527
+ }
528
+ else if (this.tombstoneMode) {
529
+ // The snapshot may contain more or fewer tombstone nodes than this client. Update tombstone state and
530
+ // notify the runtime to update its state as well.
531
+ this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
532
+ this.runtime.updateTombstonedRoutes(this.tombstones);
533
+ }
534
+ // If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
535
+ if (snapshotData === undefined) {
536
+ this.gcDataFromLastRun = undefined;
537
+ this.latestSummaryData = undefined;
538
+ return;
539
+ }
540
+ // Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
541
+ // to the GC data from the snapshot data.
542
+ const gcNodes = {};
543
+ for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
544
+ if (nodeData.unreferencedTimestampMs !== undefined) {
545
+ this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
546
+ }
547
+ gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
548
+ }
549
+ this.gcDataFromLastRun = { gcNodes };
550
+ // If tracking state across summaries, update latest summary data from the snapshot's GC data.
551
+ if (this.trackGCState) {
552
+ this.latestSummaryData = {
553
+ serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
554
+ serializedTombstones: JSON.stringify(snapshotData.tombstones),
555
+ serializedDeletedNodes: JSON.stringify(snapshotData.deletedNodes),
556
+ };
557
+ }
473
558
  }
474
559
  /**
475
560
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -503,7 +588,9 @@ export class GarbageCollector {
503
588
  var _a;
504
589
  const fullGC = (_a = options.fullGC) !== null && _a !== void 0 ? _a : (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
505
590
  const logger = options.logger
506
- ? ChildLogger.create(options.logger, undefined, { all: { completedGCRuns: () => this.completedRuns } })
591
+ ? ChildLogger.create(options.logger, undefined, {
592
+ all: { completedGCRuns: () => this.completedRuns },
593
+ })
507
594
  : this.mc.logger;
508
595
  /**
509
596
  * If there is no current reference timestamp, skip running GC. We need the current timestamp to track
@@ -555,13 +642,13 @@ export class GarbageCollector {
555
642
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
556
643
  // involving access to deleted data.
557
644
  if (this.testMode) {
558
- this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
645
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
559
646
  }
560
647
  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 */);
648
+ // If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
649
+ // involving access to "deleted" data without actually deleting the data from summaries.
650
+ // Note: we will not tombstone in test mode.
651
+ this.runtime.updateTombstonedRoutes(this.tombstones);
565
652
  }
566
653
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
567
654
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -576,30 +663,44 @@ export class GarbageCollector {
576
663
  */
577
664
  summarize(fullTree, trackState, telemetryContext) {
578
665
  var _a;
579
- if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
666
+ if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
580
667
  return;
581
668
  }
582
669
  const gcState = { gcNodes: {} };
583
- for (const [nodeId, outboundRoutes] of Object.entries(this.previousGCDataFromLastRun.gcNodes)) {
670
+ for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
584
671
  gcState.gcNodes[nodeId] = {
585
672
  outboundRoutes,
586
673
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
587
674
  };
588
675
  }
589
676
  const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
677
+ // Serialize and write deleted nodes, if any. This is done irrespective of whether sweep is enabled or not so
678
+ // to identify deleted nodes' usage.
679
+ const serializedDeletedNodes = this.deletedNodes.size > 0
680
+ ? JSON.stringify(Array.from(this.deletedNodes).sort())
681
+ : undefined;
682
+ // If running in tombstone mode, serialize and write tombstones, if any.
590
683
  const serializedTombstones = this.tombstoneMode
591
- ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
684
+ ? this.tombstones.length > 0
685
+ ? JSON.stringify(this.tombstones.sort())
686
+ : undefined
592
687
  : undefined;
593
688
  /**
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.
689
+ * Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
690
+ * write summary handle instead of summary tree for GC.
691
+ * Otherwise, write the GC summary tree. In the tree, for each of these that changed, write a summary blob and
692
+ * for each of these that did not change, write a summary handle.
596
693
  */
597
694
  if (this.trackGCState) {
598
- this.pendingSummaryData = { serializedGCState, serializedTombstones };
695
+ this.pendingSummaryData = {
696
+ serializedGCState,
697
+ serializedTombstones,
698
+ serializedDeletedNodes,
699
+ };
599
700
  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) {
701
+ // If nothing changed since last summary, send a summary handle for the entire GC data.
702
+ if (this.latestSummaryData.serializedGCState === serializedGCState &&
703
+ this.latestSummaryData.serializedTombstones === serializedTombstones) {
603
704
  const stats = mergeStats();
604
705
  stats.handleNodeCount++;
605
706
  return {
@@ -611,24 +712,25 @@ export class GarbageCollector {
611
712
  stats,
612
713
  };
613
714
  }
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 */);
715
+ // If some state changed, build a GC summary tree.
716
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
616
717
  }
617
718
  }
618
719
  // If not tracking GC state, build a GC summary tree without any summary handles.
619
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
720
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
620
721
  }
621
722
  /**
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.
723
+ * Builds the GC summary tree which contains GC state, deleted nodes and tombstones.
724
+ * If trackState is false, all of GC state, deleted nodes and tombstones are written as summary blobs.
725
+ * If trackState is true, only states that changed are written. Rest are written as handles.
625
726
  * @param serializedGCState - The GC state serialized as string.
626
- * @param serializedTombstones - THe tombstone state serialized as string.
727
+ * @param serializedTombstones - The tombstone state serialized as string.
728
+ * @param serializedDeletedNodes - Deleted nodes serialized as string.
627
729
  * @param trackState - Whether we are tracking GC state across summaries.
628
730
  * @returns the GC summary tree.
629
731
  */
630
- buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
631
- var _a, _b;
732
+ buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
733
+ var _a, _b, _c;
632
734
  const gcStateBlobKey = `${gcBlobPrefix}_root`;
633
735
  const builder = new SummaryTreeBuilder();
634
736
  // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
@@ -638,16 +740,28 @@ export class GarbageCollector {
638
740
  else {
639
741
  builder.addBlob(gcStateBlobKey, serializedGCState);
640
742
  }
641
- // If there is no tombstone data, return only the GC state.
642
- if (serializedTombstones === undefined) {
743
+ // If tombstones exist, write a summary handle if it hasn't changed. If it has changed, write a
744
+ // summary blob.
745
+ if (serializedTombstones !== undefined) {
746
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones &&
747
+ trackState) {
748
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
749
+ }
750
+ else {
751
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
752
+ }
753
+ }
754
+ // If there are no deleted nodes, return the summary tree.
755
+ if (serializedDeletedNodes === undefined) {
643
756
  return builder.getSummaryTree();
644
757
  }
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}`);
758
+ // If the deleted nodes hasn't changed, write a summary handle, else write a summary blob for it.
759
+ if (((_c = this.latestSummaryData) === null || _c === void 0 ? void 0 : _c.serializedDeletedNodes) === serializedDeletedNodes &&
760
+ trackState) {
761
+ builder.addHandle(gcDeletedBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcDeletedBlobKey}`);
648
762
  }
649
763
  else {
650
- builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
764
+ builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
651
765
  }
652
766
  return builder.getSummaryTree();
653
767
  }
@@ -664,50 +778,58 @@ export class GarbageCollector {
664
778
  };
665
779
  }
666
780
  /**
667
- * Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
668
- * to initialize the GC state of the nodes.
781
+ * Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
782
+ * in the container.
669
783
  */
670
784
  async getBaseGCDetails() {
671
785
  return this.baseGCDetailsP;
672
786
  }
673
787
  /**
674
- * Called when the latest summary of the system has been refreshed. This will be used to update the state of the
675
- * latest summary tracked.
788
+ * Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
789
+ * is downloaded and should be used to update the state.
676
790
  */
677
- async latestSummaryStateRefreshed(result, readAndParseBlob) {
678
- if (!this.shouldRunGC || !result.latestSummaryUpdated) {
791
+ async refreshLatestSummary(result, proposalHandle, summaryRefSeq, readAndParseBlob) {
792
+ // If the latest summary was updated and the summary was tracked, this client is the one that generated this
793
+ // summary. So, update wasGCRunInLatestSummary.
794
+ // Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
795
+ // true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
796
+ if (result.latestSummaryUpdated && result.wasSummaryTracked) {
797
+ this.wasGCRunInLatestSummary = this.shouldRunGC;
798
+ }
799
+ if (!result.latestSummaryUpdated || !this.shouldRunGC) {
679
800
  return;
680
801
  }
681
802
  // If the summary was tracked by this client, it was the one that generated the summary in the first place.
682
- // Basically, it was written in the current GC version.
803
+ // Update latest state from pending.
683
804
  if (result.wasSummaryTracked) {
684
805
  this.latestSummaryGCVersion = this.currentGCVersion;
685
- this.initialStateNeedsReset = false;
686
806
  if (this.trackGCState) {
687
807
  this.latestSummaryData = this.pendingSummaryData;
688
808
  this.pendingSummaryData = undefined;
689
809
  }
690
810
  return;
691
811
  }
692
- // If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
693
- // result as that is now the latest summary.
812
+ // If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
694
813
  const snapshot = result.snapshot;
695
814
  const metadataBlobId = snapshot.blobs[metadataBlobName];
696
815
  if (metadataBlobId) {
697
816
  const metadata = await readAndParseBlob(metadataBlobId);
698
817
  this.latestSummaryGCVersion = getGCVersion(metadata);
699
818
  }
700
- const gcSnapshotTree = snapshot.trees[gcTreeKey];
701
- if (gcSnapshotTree !== undefined && this.trackGCState) {
702
- const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
703
- this.latestSummaryData = {
704
- serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
705
- serializedTombstones: JSON.stringify(latestGCData.tombstones),
706
- };
819
+ // The current reference timestamp should be available if we are refreshing state from a snapshot. There has
820
+ // to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
821
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
822
+ if (currentReferenceTimestampMs === undefined) {
823
+ throw DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) });
707
824
  }
708
- else {
709
- this.latestSummaryData = undefined;
825
+ const gcSnapshotTree = snapshot.trees[gcTreeKey];
826
+ // If GC ran in the container that generated this snapshot, it will have a GC tree.
827
+ this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
828
+ let latestGCData;
829
+ if (gcSnapshotTree !== undefined) {
830
+ latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
710
831
  }
832
+ this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
711
833
  this.pendingSummaryData = undefined;
712
834
  }
713
835
  /**
@@ -746,6 +868,30 @@ export class GarbageCollector {
746
868
  if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
747
869
  this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
748
870
  }
871
+ if (this.tombstones.includes(toNodePath)) {
872
+ const nodeType = this.runtime.getNodeType(toNodePath);
873
+ let eventName = "GC_Tombstone_SubDatastore_Revived";
874
+ if (nodeType === GCNodeType.DataStore) {
875
+ eventName = "GC_Tombstone_Datastore_Revived";
876
+ }
877
+ else if (nodeType === GCNodeType.Blob) {
878
+ eventName = "GC_Tombstone_Blob_Revived";
879
+ }
880
+ sendGCUnexpectedUsageEvent(this.mc, {
881
+ eventName,
882
+ category: "generic",
883
+ isSummarizerClient: this.isSummarizerClient,
884
+ url: trimLeadingSlashes(toNodePath),
885
+ nodeType,
886
+ }, undefined /* packagePath */);
887
+ }
888
+ }
889
+ /**
890
+ * Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
891
+ * cases where objects are used after they are deleted and throw / log errors accordingly.
892
+ */
893
+ isNodeDeleted(nodePath) {
894
+ return this.deletedNodes.has(nodePath);
749
895
  }
750
896
  dispose() {
751
897
  var _a;
@@ -762,7 +908,7 @@ export class GarbageCollector {
762
908
  * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
763
909
  */
764
910
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
765
- this.previousGCDataFromLastRun = cloneGCData(gcData);
911
+ this.gcDataFromLastRun = cloneGCData(gcData);
766
912
  this.tombstones = [];
767
913
  this.newReferencesSinceLastRun.clear();
768
914
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
@@ -811,19 +957,18 @@ export class GarbageCollector {
811
957
  */
812
958
  updateStateSinceLastRun(currentGCData, logger) {
813
959
  // If we haven't run GC before there is nothing to do.
814
- if (this.previousGCDataFromLastRun === undefined) {
960
+ if (this.gcDataFromLastRun === undefined) {
815
961
  return;
816
962
  }
817
963
  // Find any references that haven't been identified correctly.
818
- const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
964
+ const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
819
965
  if (missingExplicitReferences.length > 0) {
820
966
  missingExplicitReferences.forEach((missingExplicitReference) => {
821
- const event = {
967
+ logger.sendErrorEvent({
822
968
  eventName: "gcUnknownOutboundReferences",
823
969
  gcNodeId: missingExplicitReference[0],
824
970
  gcRoutes: JSON.stringify(missingExplicitReference[1]),
825
- };
826
- logger.sendPerformanceEvent(event);
971
+ });
827
972
  });
828
973
  }
829
974
  // No references were added since the last run so we don't have to update reference states of any unreferenced
@@ -847,7 +992,7 @@ export class GarbageCollector {
847
992
  * - We don't require DDSes handles to be stored in a referenced DDS.
848
993
  * - A new data store may have "root" DDSes already created and we don't detect them today.
849
994
  */
850
- const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
995
+ const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
851
996
  const newOutboundRoutesSinceLastRun = [];
852
997
  this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
853
998
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
@@ -865,7 +1010,10 @@ export class GarbageCollector {
865
1010
  * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
866
1011
  * unreferenced and add unreferenced state.
867
1012
  */
868
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
1013
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
1014
+ "/",
1015
+ ...newOutboundRoutesSinceLastRun,
1016
+ ]);
869
1017
  for (const nodeId of gcResult.referencedNodeIds) {
870
1018
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
871
1019
  if (nodeStateTracker !== undefined) {
@@ -890,7 +1038,7 @@ export class GarbageCollector {
890
1038
  * @returns - a list of missing explicit references
891
1039
  */
892
1040
  findMissingExplicitReferences(currentGCData, previousGCData, explicitReferences) {
893
- assert(previousGCData !== undefined, 0x2b7);
1041
+ assert(previousGCData !== undefined, 0x2b7 /* "Can't validate correctness without GC data from last run" */);
894
1042
  const currentGraph = Object.entries(currentGCData.gcNodes);
895
1043
  const missingExplicitReferences = [];
896
1044
  currentGraph.forEach(([nodeId, currentOutboundRoutes]) => {
@@ -898,14 +1046,20 @@ export class GarbageCollector {
898
1046
  const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
899
1047
  const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
900
1048
  const missingExplicitRoutes = [];
1049
+ /**
1050
+ * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
1051
+ * explicit references should be added to missing explicit routes list.
1052
+ * 2. Only include data store and blob routes since GC only works for these two.
1053
+ * Note: Due to a bug with de-duped blobs, only adding data store routes for now.
1054
+ * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
1055
+ * explicit routes to them.
1056
+ */
901
1057
  currentOutboundRoutes.forEach((route) => {
902
- const isBlobOrDataStoreRoute = this.runtime.getNodeType(route) === GCNodeType.Blob ||
903
- this.runtime.getNodeType(route) === GCNodeType.DataStore;
904
- // Ignore implicitly added DDS routes to their parent datastores
905
- const notRouteFromDDSToParentDataStore = !nodeId.startsWith(route);
906
- if (isBlobOrDataStoreRoute &&
907
- notRouteFromDDSToParentDataStore &&
908
- (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
1058
+ const nodeType = this.runtime.getNodeType(route);
1059
+ if ((nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&
1060
+ !nodeId.startsWith(route) &&
1061
+ !previousRoutes.includes(route) &&
1062
+ !explicitRoutes.includes(route)) {
909
1063
  missingExplicitRoutes.push(route);
910
1064
  }
911
1065
  });
@@ -937,7 +1091,7 @@ export class GarbageCollector {
937
1091
  gcStats.nodeCount++;
938
1092
  // If there is no previous GC data, every node's state is generated and is considered as updated.
939
1093
  // Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
940
- const stateUpdated = this.previousGCDataFromLastRun === undefined ||
1094
+ const stateUpdated = this.gcDataFromLastRun === undefined ||
941
1095
  this.unreferencedNodesState.has(nodeId) === referenced;
942
1096
  if (stateUpdated) {
943
1097
  gcStats.updatedNodeCount++;
@@ -977,7 +1131,8 @@ export class GarbageCollector {
977
1131
  * this will give us a view into how much deleted content a container has.
978
1132
  */
979
1133
  logSweepEvents(logger, currentReferenceTimestampMs) {
980
- if (this.mc.config.getBoolean(disableSweepLogKey) === true || this.sweepTimeoutMs === undefined) {
1134
+ if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
1135
+ this.sweepTimeoutMs === undefined) {
981
1136
  return;
982
1137
  }
983
1138
  this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
@@ -1012,7 +1167,8 @@ export class GarbageCollector {
1012
1167
  // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
1013
1168
  // logging as nothing interesting would have happened worth logging.
1014
1169
  // If the node is active, skip logging.
1015
- if (currentReferenceTimestampMs === undefined || nodeStateTracker.state === UnreferencedState.Active) {
1170
+ if (currentReferenceTimestampMs === undefined ||
1171
+ nodeStateTracker.state === UnreferencedState.Active) {
1016
1172
  return;
1017
1173
  }
1018
1174
  // We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
@@ -1044,9 +1200,17 @@ export class GarbageCollector {
1044
1200
  // Events generated:
1045
1201
  // InactiveObject_Loaded, SweepReadyObject_Loaded
1046
1202
  if (usageType === "Loaded") {
1047
- this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() }));
1203
+ const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() });
1204
+ // Do not log the inactive object x events as error events as they are not the best signal for
1205
+ // detecting something wrong with GC either from the partner or from the runtime itself.
1206
+ if (state === UnreferencedState.Inactive) {
1207
+ this.mc.logger.sendTelemetryEvent(event);
1208
+ }
1209
+ else {
1210
+ this.mc.logger.sendErrorEvent(event);
1211
+ }
1048
1212
  }
1049
- // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
1213
+ // If SweepReady Usage Detection is enabled, the handler may close the interactive container.
1050
1214
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
1051
1215
  // and errors will arise elsewhere in the runtime
1052
1216
  if (state === UnreferencedState.SweepReady) {
@@ -1069,43 +1233,29 @@ export class GarbageCollector {
1069
1233
  * revived and a Revived event will be logged for it.
1070
1234
  */
1071
1235
  const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
1072
- const active = nodeStateTracker === undefined || nodeStateTracker.state === UnreferencedState.Active;
1236
+ const active = nodeStateTracker === undefined ||
1237
+ nodeStateTracker.state === UnreferencedState.Active;
1073
1238
  if ((usageType === "Revived") === active) {
1074
1239
  const pkg = await this.getNodePackagePath(eventProps.id);
1075
- const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
1076
- logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined, fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined }));
1240
+ const fromPkg = eventProps.fromId
1241
+ ? await this.getNodePackagePath(eventProps.fromId)
1242
+ : undefined;
1243
+ const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg
1244
+ ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
1245
+ : undefined, fromPkg: fromPkg
1246
+ ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
1247
+ : undefined });
1248
+ if (state === UnreferencedState.Inactive) {
1249
+ logger.sendTelemetryEvent(event);
1250
+ }
1251
+ else {
1252
+ logger.sendErrorEvent(event);
1253
+ }
1077
1254
  }
1078
1255
  }
1079
1256
  this.pendingEventsQueue = [];
1080
1257
  }
1081
1258
  }
1082
- /**
1083
- * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1084
- * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1085
- */
1086
- async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1087
- let rootGCState = { gcNodes: {} };
1088
- let tombstones;
1089
- for (const key of Object.keys(gcSnapshotTree.blobs)) {
1090
- if (key === gcTombstoneBlobKey) {
1091
- tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1092
- continue;
1093
- }
1094
- // Skip blobs that do not start with the GC prefix.
1095
- if (!key.startsWith(gcBlobPrefix)) {
1096
- continue;
1097
- }
1098
- const blobId = gcSnapshotTree.blobs[key];
1099
- if (blobId === undefined) {
1100
- continue;
1101
- }
1102
- const gcState = await readAndParseBlob(blobId);
1103
- assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
1104
- // Merge the GC state of this blob into the root GC state.
1105
- rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1106
- }
1107
- return { gcState: rootGCState, tombstones };
1108
- }
1109
1259
  function generateSortedGCState(gcState) {
1110
1260
  const sortableArray = Object.entries(gcState.gcNodes);
1111
1261
  sortableArray.sort(([a], [b]) => a.localeCompare(b));
@@ -1120,7 +1270,9 @@ function generateSortedGCState(gcState) {
1120
1270
  class TimerWithNoDefaultTimeout extends Timer {
1121
1271
  constructor(callback) {
1122
1272
  // The default timeout/handlers will never be used since start/restart pass overrides below
1123
- super(0, () => { throw new Error("DefaultHandler should not be used"); });
1273
+ super(0, () => {
1274
+ throw new Error("DefaultHandler should not be used");
1275
+ });
1124
1276
  this.callback = callback;
1125
1277
  }
1126
1278
  start(timeoutMs) {