@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 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 (365) 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 +62 -28
  9. package/dist/blobManager.d.ts.map +1 -1
  10. package/dist/blobManager.js +256 -102
  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 +110 -78
  19. package/dist/containerRuntime.d.ts.map +1 -1
  20. package/dist/containerRuntime.js +336 -331
  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 +40 -23
  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 +69 -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 +57 -42
  43. package/dist/garbageCollection.d.ts.map +1 -1
  44. package/dist/garbageCollection.js +371 -239
  45. package/dist/garbageCollection.js.map +1 -1
  46. package/dist/garbageCollectionConstants.d.ts +23 -0
  47. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  48. package/dist/garbageCollectionConstants.js +36 -0
  49. package/dist/garbageCollectionConstants.js.map +1 -0
  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 +15 -11
  57. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  58. package/dist/index.d.ts +4 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +5 -6
  61. package/dist/index.js.map +1 -1
  62. package/dist/opLifecycle/batchManager.d.ts +42 -0
  63. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  64. package/dist/opLifecycle/batchManager.js +124 -0
  65. package/dist/opLifecycle/batchManager.js.map +1 -0
  66. package/dist/opLifecycle/definitions.d.ts +64 -0
  67. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  68. package/dist/opLifecycle/definitions.js +7 -0
  69. package/dist/opLifecycle/definitions.js.map +1 -0
  70. package/dist/opLifecycle/index.d.ts +12 -0
  71. package/dist/opLifecycle/index.d.ts.map +1 -0
  72. package/dist/opLifecycle/index.js +22 -0
  73. package/dist/opLifecycle/index.js.map +1 -0
  74. package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
  75. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  76. package/dist/opLifecycle/opCompressor.js +67 -0
  77. package/dist/opLifecycle/opCompressor.js.map +1 -0
  78. package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
  79. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  80. package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
  81. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  82. package/dist/opLifecycle/opSplitter.d.ts +49 -0
  83. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  84. package/dist/opLifecycle/opSplitter.js +173 -0
  85. package/dist/opLifecycle/opSplitter.js.map +1 -0
  86. package/dist/opLifecycle/outbox.d.ts +52 -0
  87. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  88. package/dist/opLifecycle/outbox.js +164 -0
  89. package/dist/opLifecycle/outbox.js.map +1 -0
  90. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  91. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  92. package/dist/opLifecycle/remoteMessageProcessor.js +96 -0
  93. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  94. package/dist/opProperties.d.ts.map +1 -1
  95. package/dist/opProperties.js +1 -3
  96. package/dist/opProperties.js.map +1 -1
  97. package/dist/orderedClientElection.d.ts.map +1 -1
  98. package/dist/orderedClientElection.js +10 -4
  99. package/dist/orderedClientElection.js.map +1 -1
  100. package/dist/packageVersion.d.ts +1 -1
  101. package/dist/packageVersion.js +1 -1
  102. package/dist/packageVersion.js.map +1 -1
  103. package/dist/pendingStateManager.d.ts +4 -13
  104. package/dist/pendingStateManager.d.ts.map +1 -1
  105. package/dist/pendingStateManager.js +134 -161
  106. package/dist/pendingStateManager.js.map +1 -1
  107. package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
  108. package/dist/runWhileConnectedCoordinator.js.map +1 -1
  109. package/dist/runningSummarizer.d.ts.map +1 -1
  110. package/dist/runningSummarizer.js +34 -22
  111. package/dist/runningSummarizer.js.map +1 -1
  112. package/dist/scheduleManager.d.ts +0 -1
  113. package/dist/scheduleManager.d.ts.map +1 -1
  114. package/dist/scheduleManager.js +11 -21
  115. package/dist/scheduleManager.js.map +1 -1
  116. package/dist/serializedSnapshotStorage.d.ts.map +1 -1
  117. package/dist/serializedSnapshotStorage.js +3 -1
  118. package/dist/serializedSnapshotStorage.js.map +1 -1
  119. package/dist/summarizer.d.ts +2 -3
  120. package/dist/summarizer.d.ts.map +1 -1
  121. package/dist/summarizer.js +39 -18
  122. package/dist/summarizer.js.map +1 -1
  123. package/dist/summarizerClientElection.d.ts +1 -2
  124. package/dist/summarizerClientElection.d.ts.map +1 -1
  125. package/dist/summarizerClientElection.js +3 -30
  126. package/dist/summarizerClientElection.js.map +1 -1
  127. package/dist/summarizerHandle.d.ts.map +1 -1
  128. package/dist/summarizerHandle.js.map +1 -1
  129. package/dist/summarizerHeuristics.d.ts.map +1 -1
  130. package/dist/summarizerHeuristics.js +6 -9
  131. package/dist/summarizerHeuristics.js.map +1 -1
  132. package/dist/summarizerTypes.d.ts +22 -25
  133. package/dist/summarizerTypes.d.ts.map +1 -1
  134. package/dist/summarizerTypes.js.map +1 -1
  135. package/dist/summaryCollection.d.ts.map +1 -1
  136. package/dist/summaryCollection.js +18 -8
  137. package/dist/summaryCollection.js.map +1 -1
  138. package/dist/summaryFormat.d.ts.map +1 -1
  139. package/dist/summaryFormat.js +18 -11
  140. package/dist/summaryFormat.js.map +1 -1
  141. package/dist/summaryGenerator.d.ts.map +1 -1
  142. package/dist/summaryGenerator.js +32 -14
  143. package/dist/summaryGenerator.js.map +1 -1
  144. package/dist/summaryManager.d.ts.map +1 -1
  145. package/dist/summaryManager.js +21 -9
  146. package/dist/summaryManager.js.map +1 -1
  147. package/dist/throttler.d.ts +2 -2
  148. package/dist/throttler.d.ts.map +1 -1
  149. package/dist/throttler.js +4 -4
  150. package/dist/throttler.js.map +1 -1
  151. package/garbageCollection.md +15 -2
  152. package/lib/batchTracker.d.ts +1 -2
  153. package/lib/batchTracker.d.ts.map +1 -1
  154. package/lib/batchTracker.js +2 -1
  155. package/lib/batchTracker.js.map +1 -1
  156. package/lib/blobManager.d.ts +62 -28
  157. package/lib/blobManager.d.ts.map +1 -1
  158. package/lib/blobManager.js +259 -105
  159. package/lib/blobManager.js.map +1 -1
  160. package/lib/connectionTelemetry.d.ts.map +1 -1
  161. package/lib/connectionTelemetry.js +11 -9
  162. package/lib/connectionTelemetry.js.map +1 -1
  163. package/lib/containerHandleContext.d.ts.map +1 -1
  164. package/lib/containerHandleContext.js +3 -1
  165. package/lib/containerHandleContext.js.map +1 -1
  166. package/lib/containerRuntime.d.ts +110 -78
  167. package/lib/containerRuntime.d.ts.map +1 -1
  168. package/lib/containerRuntime.js +340 -334
  169. package/lib/containerRuntime.js.map +1 -1
  170. package/lib/dataStore.d.ts.map +1 -1
  171. package/lib/dataStore.js +11 -9
  172. package/lib/dataStore.js.map +1 -1
  173. package/lib/dataStoreContext.d.ts +2 -1
  174. package/lib/dataStoreContext.d.ts.map +1 -1
  175. package/lib/dataStoreContext.js +41 -24
  176. package/lib/dataStoreContext.js.map +1 -1
  177. package/lib/dataStoreContexts.d.ts.map +1 -1
  178. package/lib/dataStoreContexts.js +7 -3
  179. package/lib/dataStoreContexts.js.map +1 -1
  180. package/lib/dataStoreRegistry.d.ts.map +1 -1
  181. package/lib/dataStoreRegistry.js +3 -1
  182. package/lib/dataStoreRegistry.js.map +1 -1
  183. package/lib/dataStores.d.ts +12 -9
  184. package/lib/dataStores.d.ts.map +1 -1
  185. package/lib/dataStores.js +75 -52
  186. package/lib/dataStores.js.map +1 -1
  187. package/lib/deltaScheduler.d.ts.map +1 -1
  188. package/lib/deltaScheduler.js +9 -4
  189. package/lib/deltaScheduler.js.map +1 -1
  190. package/lib/garbageCollection.d.ts +57 -42
  191. package/lib/garbageCollection.d.ts.map +1 -1
  192. package/lib/garbageCollection.js +364 -232
  193. package/lib/garbageCollection.js.map +1 -1
  194. package/lib/garbageCollectionConstants.d.ts +23 -0
  195. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  196. package/lib/garbageCollectionConstants.js +33 -0
  197. package/lib/garbageCollectionConstants.js.map +1 -0
  198. package/lib/garbageCollectionHelpers.d.ts +15 -0
  199. package/lib/garbageCollectionHelpers.d.ts.map +1 -0
  200. package/lib/garbageCollectionHelpers.js +23 -0
  201. package/lib/garbageCollectionHelpers.js.map +1 -0
  202. package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
  203. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  204. package/lib/gcSweepReadyUsageDetection.js +15 -11
  205. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  206. package/lib/index.d.ts +4 -3
  207. package/lib/index.d.ts.map +1 -1
  208. package/lib/index.js +3 -3
  209. package/lib/index.js.map +1 -1
  210. package/lib/opLifecycle/batchManager.d.ts +42 -0
  211. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  212. package/lib/opLifecycle/batchManager.js +120 -0
  213. package/lib/opLifecycle/batchManager.js.map +1 -0
  214. package/lib/opLifecycle/definitions.d.ts +64 -0
  215. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  216. package/lib/opLifecycle/definitions.js +6 -0
  217. package/lib/opLifecycle/definitions.js.map +1 -0
  218. package/lib/opLifecycle/index.d.ts +12 -0
  219. package/lib/opLifecycle/index.d.ts.map +1 -0
  220. package/lib/opLifecycle/index.js +11 -0
  221. package/lib/opLifecycle/index.js.map +1 -0
  222. package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
  223. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  224. package/lib/opLifecycle/opCompressor.js +63 -0
  225. package/lib/opLifecycle/opCompressor.js.map +1 -0
  226. package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
  227. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  228. package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
  229. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  230. package/lib/opLifecycle/opSplitter.d.ts +49 -0
  231. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  232. package/lib/opLifecycle/opSplitter.js +168 -0
  233. package/lib/opLifecycle/opSplitter.js.map +1 -0
  234. package/lib/opLifecycle/outbox.d.ts +52 -0
  235. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  236. package/lib/opLifecycle/outbox.js +160 -0
  237. package/lib/opLifecycle/outbox.js.map +1 -0
  238. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  239. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  240. package/lib/opLifecycle/remoteMessageProcessor.js +91 -0
  241. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  242. package/lib/opProperties.d.ts.map +1 -1
  243. package/lib/opProperties.js +1 -3
  244. package/lib/opProperties.js.map +1 -1
  245. package/lib/orderedClientElection.d.ts.map +1 -1
  246. package/lib/orderedClientElection.js +10 -4
  247. package/lib/orderedClientElection.js.map +1 -1
  248. package/lib/packageVersion.d.ts +1 -1
  249. package/lib/packageVersion.js +1 -1
  250. package/lib/packageVersion.js.map +1 -1
  251. package/lib/pendingStateManager.d.ts +4 -13
  252. package/lib/pendingStateManager.d.ts.map +1 -1
  253. package/lib/pendingStateManager.js +134 -161
  254. package/lib/pendingStateManager.js.map +1 -1
  255. package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
  256. package/lib/runWhileConnectedCoordinator.js.map +1 -1
  257. package/lib/runningSummarizer.d.ts.map +1 -1
  258. package/lib/runningSummarizer.js +35 -23
  259. package/lib/runningSummarizer.js.map +1 -1
  260. package/lib/scheduleManager.d.ts +0 -1
  261. package/lib/scheduleManager.d.ts.map +1 -1
  262. package/lib/scheduleManager.js +11 -21
  263. package/lib/scheduleManager.js.map +1 -1
  264. package/lib/serializedSnapshotStorage.d.ts.map +1 -1
  265. package/lib/serializedSnapshotStorage.js +3 -1
  266. package/lib/serializedSnapshotStorage.js.map +1 -1
  267. package/lib/summarizer.d.ts +2 -3
  268. package/lib/summarizer.d.ts.map +1 -1
  269. package/lib/summarizer.js +39 -18
  270. package/lib/summarizer.js.map +1 -1
  271. package/lib/summarizerClientElection.d.ts +1 -2
  272. package/lib/summarizerClientElection.d.ts.map +1 -1
  273. package/lib/summarizerClientElection.js +3 -30
  274. package/lib/summarizerClientElection.js.map +1 -1
  275. package/lib/summarizerHandle.d.ts.map +1 -1
  276. package/lib/summarizerHandle.js.map +1 -1
  277. package/lib/summarizerHeuristics.d.ts.map +1 -1
  278. package/lib/summarizerHeuristics.js +6 -9
  279. package/lib/summarizerHeuristics.js.map +1 -1
  280. package/lib/summarizerTypes.d.ts +22 -25
  281. package/lib/summarizerTypes.d.ts.map +1 -1
  282. package/lib/summarizerTypes.js.map +1 -1
  283. package/lib/summaryCollection.d.ts.map +1 -1
  284. package/lib/summaryCollection.js +18 -8
  285. package/lib/summaryCollection.js.map +1 -1
  286. package/lib/summaryFormat.d.ts.map +1 -1
  287. package/lib/summaryFormat.js +20 -13
  288. package/lib/summaryFormat.js.map +1 -1
  289. package/lib/summaryGenerator.d.ts.map +1 -1
  290. package/lib/summaryGenerator.js +32 -14
  291. package/lib/summaryGenerator.js.map +1 -1
  292. package/lib/summaryManager.d.ts.map +1 -1
  293. package/lib/summaryManager.js +21 -9
  294. package/lib/summaryManager.js.map +1 -1
  295. package/lib/throttler.d.ts +2 -2
  296. package/lib/throttler.d.ts.map +1 -1
  297. package/lib/throttler.js +4 -4
  298. package/lib/throttler.js.map +1 -1
  299. package/package.json +28 -38
  300. package/prettier.config.cjs +1 -1
  301. package/src/batchTracker.ts +55 -50
  302. package/src/blobManager.ts +802 -541
  303. package/src/connectionTelemetry.ts +280 -249
  304. package/src/containerHandleContext.ts +27 -29
  305. package/src/containerRuntime.ts +3125 -2982
  306. package/src/dataStore.ts +172 -159
  307. package/src/dataStoreContext.ts +1049 -992
  308. package/src/dataStoreContexts.ts +178 -161
  309. package/src/dataStoreRegistry.ts +25 -20
  310. package/src/dataStores.ts +785 -711
  311. package/src/deltaScheduler.ts +158 -150
  312. package/src/garbageCollection.ts +1797 -1558
  313. package/src/garbageCollectionConstants.ts +38 -0
  314. package/src/garbageCollectionHelpers.ts +37 -0
  315. package/src/gcSweepReadyUsageDetection.ts +90 -84
  316. package/src/index.ts +68 -69
  317. package/src/opLifecycle/batchManager.ts +167 -0
  318. package/src/opLifecycle/definitions.ts +70 -0
  319. package/src/opLifecycle/index.ts +18 -0
  320. package/src/opLifecycle/opCompressor.ts +82 -0
  321. package/src/opLifecycle/opDecompressor.ts +124 -0
  322. package/src/opLifecycle/opSplitter.ts +238 -0
  323. package/src/opLifecycle/outbox.ts +228 -0
  324. package/src/opLifecycle/remoteMessageProcessor.ts +106 -0
  325. package/src/opProperties.ts +11 -9
  326. package/src/orderedClientElection.ts +489 -457
  327. package/src/packageVersion.ts +1 -1
  328. package/src/pendingStateManager.ts +379 -381
  329. package/src/runWhileConnectedCoordinator.ts +78 -71
  330. package/src/runningSummarizer.ts +619 -582
  331. package/src/scheduleManager.ts +299 -280
  332. package/src/serializedSnapshotStorage.ts +116 -111
  333. package/src/summarizer.ts +417 -381
  334. package/src/summarizerClientElection.ts +107 -129
  335. package/src/summarizerHandle.ts +11 -9
  336. package/src/summarizerHeuristics.ts +183 -186
  337. package/src/summarizerTypes.ts +344 -333
  338. package/src/summaryCollection.ts +378 -349
  339. package/src/summaryFormat.ts +146 -127
  340. package/src/summaryGenerator.ts +464 -406
  341. package/src/summaryManager.ts +377 -348
  342. package/src/throttler.ts +131 -122
  343. package/tsconfig.esnext.json +6 -6
  344. package/tsconfig.json +9 -13
  345. package/dist/batchManager.d.ts +0 -42
  346. package/dist/batchManager.d.ts.map +0 -1
  347. package/dist/batchManager.js +0 -83
  348. package/dist/batchManager.js.map +0 -1
  349. package/dist/opCompressor.d.ts.map +0 -1
  350. package/dist/opCompressor.js +0 -50
  351. package/dist/opCompressor.js.map +0 -1
  352. package/dist/opDecompressor.d.ts.map +0 -1
  353. package/dist/opDecompressor.js.map +0 -1
  354. package/lib/batchManager.d.ts +0 -42
  355. package/lib/batchManager.d.ts.map +0 -1
  356. package/lib/batchManager.js +0 -79
  357. package/lib/batchManager.js.map +0 -1
  358. package/lib/opCompressor.d.ts.map +0 -1
  359. package/lib/opCompressor.js +0 -46
  360. package/lib/opCompressor.js.map +0 -1
  361. package/lib/opDecompressor.d.ts.map +0 -1
  362. package/lib/opDecompressor.js.map +0 -1
  363. package/src/batchManager.ts +0 -108
  364. package/src/opCompressor.ts +0 -59
  365. package/src/opDecompressor.ts +0 -82
@@ -14,44 +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 { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, trackGCStateKey, } from "./garbageCollectionConstants";
26
+ import { sendGCUnexpectedUsageEvent } from "./garbageCollectionHelpers";
25
27
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
26
28
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
27
- /** This is the current version of garbage collection. */
28
- 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
- // The key for tombstone blob in the GC tree in summary.
34
- export const gcTombstoneBlobKey = "__tombstones";
35
- // Feature gate key to turn GC on / off.
36
- export const runGCKey = "Fluid.GarbageCollection.RunGC";
37
- // Feature gate key to turn GC sweep on / off.
38
- export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
39
- // Feature gate key to turn GC test mode on / off.
40
- export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
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 write the gc blob as a handle if the data is the same.
44
- export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
45
- // Feature gate key to turn GC sweep log off.
46
- export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
47
- // Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
48
- export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
49
- // Feature gate to enable throwing an error when tombstone object is used.
50
- export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
51
- // One day in milliseconds.
52
- export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
53
- export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
54
- export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
55
29
  /** The types of GC nodes in the GC reference graph. */
56
30
  export const GCNodeType = {
57
31
  // Nodes that are for data stores.
@@ -164,25 +138,13 @@ export class UnreferencedStateTracker {
164
138
  export class GarbageCollector {
165
139
  constructor(createParams) {
166
140
  var _a, _b, _c, _d, _e, _f, _g, _h;
167
- /**
168
- * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
169
- *
170
- * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
171
- * it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
172
- *
173
- * 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
174
- * a document and the first time GC is enabled after is was disabled before.
175
- *
176
- * Note that the state needs reset only for the very first time summary is generated by this client. After that, the
177
- * state will be up-to-date and this flag will be reset.
178
- */
179
- this.initialStateNeedsReset = false;
180
- // The current GC version that this container is running.
181
- this.currentGCVersion = GCVersion;
182
141
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
183
142
  // outbound routes from that node.
184
143
  this.newReferencesSinceLastRun = new Map();
144
+ // A list of nodes that have been tombstoned.
185
145
  this.tombstones = [];
146
+ // A list of nodes that have been deleted during sweep phase.
147
+ this.deletedNodes = new Set();
186
148
  // Map of node ids to their unreferenced state tracker.
187
149
  this.unreferencedNodesState = new Map();
188
150
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -202,7 +164,14 @@ export class GarbageCollector {
202
164
  const baseSnapshot = createParams.baseSnapshot;
203
165
  const metadata = createParams.metadata;
204
166
  const readAndParseBlob = createParams.readAndParseBlob;
205
- 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;
206
175
  this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
207
176
  let prevSummaryGCVersion;
208
177
  /**
@@ -216,8 +185,8 @@ export class GarbageCollector {
216
185
  function computeSweepTimeout(sessionExpiryTimeoutMs) {
217
186
  const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
218
187
  const bufferMs = oneDayMs;
219
- return sessionExpiryTimeoutMs &&
220
- (sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
188
+ return (sessionExpiryTimeoutMs &&
189
+ sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
221
190
  }
222
191
  /**
223
192
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
@@ -248,11 +217,11 @@ export class GarbageCollector {
248
217
  // flag in GC options to false.
249
218
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
250
219
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
251
- // ...unless we're using the TestOverride
252
- this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
220
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
253
221
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
254
222
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
255
- 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;
256
225
  }
257
226
  this.sweepTimeoutMs =
258
227
  testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
@@ -262,7 +231,9 @@ export class GarbageCollector {
262
231
  // If Test Override config is set, override Session Expiry timeout.
263
232
  const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
264
233
  const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
265
- 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
+ });
266
237
  this.sessionExpiryTimer.start();
267
238
  }
268
239
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
@@ -277,11 +248,12 @@ export class GarbageCollector {
277
248
  *
278
249
  * These conditions can be overridden via runGCKey feature flag.
279
250
  */
280
- this.shouldRunGC = (_d = this.mc.config.getBoolean(runGCKey)) !== null && _d !== void 0 ? _d : (
281
- // GC must be enabled for the document.
282
- this.gcEnabled
283
- // GC must not be disabled via GC options.
284
- && !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);
285
257
  /**
286
258
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
287
259
  *
@@ -293,24 +265,26 @@ export class GarbageCollector {
293
265
  * feature flag.
294
266
  */
295
267
  this.shouldRunSweep =
296
- this.shouldRunGC
297
- && this.sweepTimeoutMs !== undefined
298
- && ((_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);
299
271
  this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
300
272
  // Override inactive timeout if test config or gc options to override it is set.
301
- this.inactiveTimeoutMs = (_g = (_f = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _f !== void 0 ? _f : this.gcOptions.inactiveTimeoutMs) !== null && _g !== void 0 ? _g : defaultInactiveTimeoutMs;
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;
302
275
  // Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
303
276
  if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
304
277
  throw new UsageError("inactive timeout should not be greater than the sweep timeout");
305
278
  }
306
279
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
307
- this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
308
- // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
309
- this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
310
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
311
- // contain GC tree and GC is enabled.
312
- const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
313
- this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
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;
314
288
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
315
289
  // it involves fetching blobs from storage which is expensive.
316
290
  this.baseSnapshotDataP = new LazyPromise(async () => {
@@ -327,11 +301,13 @@ export class GarbageCollector {
327
301
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
328
302
  // consolidate into IGarbageCollectionState format.
329
303
  // Add a node for the root node that is not present in older snapshot format.
330
- const gcState = { gcNodes: { "/": { outboundRoutes: [] } } };
304
+ const gcState = {
305
+ gcNodes: { "/": { outboundRoutes: [] } },
306
+ };
331
307
  const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
332
308
  assert(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
333
309
  for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
334
- const blobId = dsSnapshotTree.blobs[gcBlobKey];
310
+ const blobId = dsSnapshotTree.blobs[gcTreeKey];
335
311
  if (blobId === undefined) {
336
312
  continue;
337
313
  }
@@ -351,14 +327,19 @@ export class GarbageCollector {
351
327
  // Prefix the data store id to the GC node ids to make them relative to the root from being
352
328
  // relative to the data store. Similar to how its done in DataStore::getGCData.
353
329
  const rootId = id === "/" ? dsRootId : `${dsRootId}${id}`;
354
- gcState.gcNodes[rootId] = { outboundRoutes: Array.from(outboundRoutes) };
330
+ gcState.gcNodes[rootId] = {
331
+ outboundRoutes: Array.from(outboundRoutes),
332
+ };
355
333
  }
356
334
  assert(gcState.gcNodes[dsRootId] !== undefined, 0x2a9 /* GC nodes for data store not in GC blob */);
357
- gcState.gcNodes[dsRootId].unreferencedTimestampMs = gcSummaryDetails.unrefTimestamp;
335
+ gcState.gcNodes[dsRootId].unreferencedTimestampMs =
336
+ gcSummaryDetails.unrefTimestamp;
358
337
  }
359
338
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
360
339
  // 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, tombstones: undefined };
340
+ return Object.keys(gcState.gcNodes).length === 1
341
+ ? undefined
342
+ : { gcState, tombstones: undefined, deletedNodes: undefined };
362
343
  }
363
344
  catch (error) {
364
345
  const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -372,7 +353,6 @@ export class GarbageCollector {
372
353
  * GC state and updates their inactive or sweep ready state.
373
354
  */
374
355
  this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
375
- const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
376
356
  /**
377
357
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
378
358
  * how long objects have been unreferenced and if they can be deleted.
@@ -381,6 +361,7 @@ export class GarbageCollector {
381
361
  * for this container and it is in read mode. In this scenario, there is no point in running GC anyway
382
362
  * because references in the container do not change without any ops, i.e., there is nothing to collect.
383
363
  */
364
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
384
365
  if (currentReferenceTimestampMs === undefined) {
385
366
  // Log an event so we can evaluate how often we run into this scenario.
386
367
  this.mc.logger.sendErrorEvent({
@@ -389,38 +370,24 @@ export class GarbageCollector {
389
370
  });
390
371
  return;
391
372
  }
392
- const baseSnapshotData = await this.baseSnapshotDataP;
393
373
  /**
394
374
  * The base snapshot data will not be present if the container is loaded from:
395
375
  * 1. The first summary created by the detached container.
396
376
  * 2. A summary that was generated with GC disabled.
397
377
  * 3. A summary that was generated before GC even existed.
398
378
  */
379
+ const baseSnapshotData = await this.baseSnapshotDataP;
399
380
  if (baseSnapshotData === undefined) {
400
381
  return;
401
382
  }
402
- const gcNodes = {};
403
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
404
- if (nodeData.unreferencedTimestampMs !== undefined) {
405
- this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
406
- }
407
- gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
408
- }
409
- this.previousGCDataFromLastRun = { gcNodes };
410
- // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
411
- if (this.trackGCState) {
412
- this.latestSummaryData = {
413
- serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
414
- serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
415
- };
416
- }
383
+ this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
417
384
  });
418
- // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
419
- // 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.
420
387
  this.baseGCDetailsP = new LazyPromise(async () => {
421
388
  const baseSnapshotData = await this.baseSnapshotDataP;
422
389
  if (baseSnapshotData === undefined) {
423
- return new Map();
390
+ return {};
424
391
  }
425
392
  const gcNodes = {};
426
393
  for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
@@ -430,18 +397,7 @@ export class GarbageCollector {
430
397
  // This is an optimization for space (vs performance) wherein we don't need to store the used routes of
431
398
  // each node in the summary.
432
399
  const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
433
- const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
434
- // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
435
- // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
436
- for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
437
- if (nodeData.unreferencedTimestampMs !== undefined) {
438
- const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
439
- if (dataStoreGCDetails !== undefined) {
440
- dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
441
- }
442
- }
443
- }
444
- return baseGCDetailsMap;
400
+ return { gcData: { gcNodes }, usedRoutes };
445
401
  });
446
402
  // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
447
403
  // summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
@@ -462,39 +418,143 @@ export class GarbageCollector {
462
418
  *
463
419
  * 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
464
420
  *
465
- * 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:
466
424
  *
467
- * 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.
468
426
  *
469
- * 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.
470
428
  */
471
429
  get summaryStateNeedsReset() {
472
- return this.initialStateNeedsReset ||
473
- (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;
474
449
  }
475
450
  /** Returns a list of all the configurations for garbage collection. */
476
451
  get configs() {
477
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);
478
453
  }
479
454
  /**
480
- * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
481
- * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
482
- * in use or not.
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.
483
457
  */
484
458
  async initializeBaseState() {
485
459
  const baseSnapshotData = await this.baseSnapshotDataP;
486
460
  /**
487
- * 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:
488
462
  * 1. The first summary created by the detached container.
489
463
  * 2. A summary that was generated with GC disabled.
490
464
  * 3. A summary that was generated before GC even existed.
491
- * 4. A summary that was generated with tombstone feature disabled.
492
465
  */
493
- if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
466
+ if (baseSnapshotData === undefined) {
494
467
  return;
495
468
  }
496
- this.tombstones = baseSnapshotData.tombstones;
497
- 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
+ }
498
558
  }
499
559
  /**
500
560
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -528,7 +588,9 @@ export class GarbageCollector {
528
588
  var _a;
529
589
  const fullGC = (_a = options.fullGC) !== null && _a !== void 0 ? _a : (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
530
590
  const logger = options.logger
531
- ? ChildLogger.create(options.logger, undefined, { all: { completedGCRuns: () => this.completedRuns } })
591
+ ? ChildLogger.create(options.logger, undefined, {
592
+ all: { completedGCRuns: () => this.completedRuns },
593
+ })
532
594
  : this.mc.logger;
533
595
  /**
534
596
  * If there is no current reference timestamp, skip running GC. We need the current timestamp to track
@@ -580,13 +642,13 @@ export class GarbageCollector {
580
642
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
581
643
  // involving access to deleted data.
582
644
  if (this.testMode) {
583
- this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
645
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
584
646
  }
585
647
  else if (this.tombstoneMode) {
586
- // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
587
- // scenarios involving access to "deleted" data without actually deleting the data from summaries.
588
- // Note: we will not tombstone in test mode
589
- this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
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);
590
652
  }
591
653
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
592
654
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
@@ -601,30 +663,44 @@ export class GarbageCollector {
601
663
  */
602
664
  summarize(fullTree, trackState, telemetryContext) {
603
665
  var _a;
604
- if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
666
+ if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
605
667
  return;
606
668
  }
607
669
  const gcState = { gcNodes: {} };
608
- for (const [nodeId, outboundRoutes] of Object.entries(this.previousGCDataFromLastRun.gcNodes)) {
670
+ for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
609
671
  gcState.gcNodes[nodeId] = {
610
672
  outboundRoutes,
611
673
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
612
674
  };
613
675
  }
614
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.
615
683
  const serializedTombstones = this.tombstoneMode
616
- ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
684
+ ? this.tombstones.length > 0
685
+ ? JSON.stringify(this.tombstones.sort())
686
+ : undefined
617
687
  : undefined;
618
688
  /**
619
- * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
620
- * summary, send summary handles for them. Otherwise, send the data in summary blobs.
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.
621
693
  */
622
694
  if (this.trackGCState) {
623
- this.pendingSummaryData = { serializedGCState, serializedTombstones };
695
+ this.pendingSummaryData = {
696
+ serializedGCState,
697
+ serializedTombstones,
698
+ serializedDeletedNodes,
699
+ };
624
700
  if (trackState && !fullTree && this.latestSummaryData !== undefined) {
625
- // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
626
- if (this.latestSummaryData.serializedGCState === serializedGCState
627
- && this.latestSummaryData.serializedTombstones === serializedTombstones) {
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) {
628
704
  const stats = mergeStats();
629
705
  stats.handleNodeCount++;
630
706
  return {
@@ -636,24 +712,25 @@ export class GarbageCollector {
636
712
  stats,
637
713
  };
638
714
  }
639
- // If either or both of GC state or tombstone state changed, build a GC summary tree.
640
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
715
+ // If some state changed, build a GC summary tree.
716
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
641
717
  }
642
718
  }
643
719
  // If not tracking GC state, build a GC summary tree without any summary handles.
644
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
720
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
645
721
  }
646
722
  /**
647
- * Builds the GC summary tree which contains GC state and tombstone state.
648
- * If trackState is false, both GC state and tombstone state are written as summary blobs.
649
- * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
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.
650
726
  * @param serializedGCState - The GC state serialized as string.
651
- * @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.
652
729
  * @param trackState - Whether we are tracking GC state across summaries.
653
730
  * @returns the GC summary tree.
654
731
  */
655
- buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
656
- var _a, _b;
732
+ buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
733
+ var _a, _b, _c;
657
734
  const gcStateBlobKey = `${gcBlobPrefix}_root`;
658
735
  const builder = new SummaryTreeBuilder();
659
736
  // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
@@ -663,16 +740,28 @@ export class GarbageCollector {
663
740
  else {
664
741
  builder.addBlob(gcStateBlobKey, serializedGCState);
665
742
  }
666
- // If there is no tombstone data, return only the GC state.
667
- 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) {
668
756
  return builder.getSummaryTree();
669
757
  }
670
- // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
671
- if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
672
- builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
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}`);
673
762
  }
674
763
  else {
675
- builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
764
+ builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
676
765
  }
677
766
  return builder.getSummaryTree();
678
767
  }
@@ -689,50 +778,58 @@ export class GarbageCollector {
689
778
  };
690
779
  }
691
780
  /**
692
- * Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
693
- * 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.
694
783
  */
695
784
  async getBaseGCDetails() {
696
785
  return this.baseGCDetailsP;
697
786
  }
698
787
  /**
699
- * Called when the latest summary of the system has been refreshed. This will be used to update the state of the
700
- * 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.
701
790
  */
702
- async latestSummaryStateRefreshed(result, readAndParseBlob) {
703
- 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) {
704
800
  return;
705
801
  }
706
802
  // If the summary was tracked by this client, it was the one that generated the summary in the first place.
707
- // Basically, it was written in the current GC version.
803
+ // Update latest state from pending.
708
804
  if (result.wasSummaryTracked) {
709
805
  this.latestSummaryGCVersion = this.currentGCVersion;
710
- this.initialStateNeedsReset = false;
711
806
  if (this.trackGCState) {
712
807
  this.latestSummaryData = this.pendingSummaryData;
713
808
  this.pendingSummaryData = undefined;
714
809
  }
715
810
  return;
716
811
  }
717
- // If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
718
- // 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.
719
813
  const snapshot = result.snapshot;
720
814
  const metadataBlobId = snapshot.blobs[metadataBlobName];
721
815
  if (metadataBlobId) {
722
816
  const metadata = await readAndParseBlob(metadataBlobId);
723
817
  this.latestSummaryGCVersion = getGCVersion(metadata);
724
818
  }
725
- const gcSnapshotTree = snapshot.trees[gcTreeKey];
726
- if (gcSnapshotTree !== undefined && this.trackGCState) {
727
- const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
728
- this.latestSummaryData = {
729
- serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
730
- serializedTombstones: JSON.stringify(latestGCData.tombstones),
731
- };
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) });
732
824
  }
733
- else {
734
- 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);
735
831
  }
832
+ this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
736
833
  this.pendingSummaryData = undefined;
737
834
  }
738
835
  /**
@@ -771,6 +868,30 @@ export class GarbageCollector {
771
868
  if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
772
869
  this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
773
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);
774
895
  }
775
896
  dispose() {
776
897
  var _a;
@@ -787,7 +908,7 @@ export class GarbageCollector {
787
908
  * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
788
909
  */
789
910
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
790
- this.previousGCDataFromLastRun = cloneGCData(gcData);
911
+ this.gcDataFromLastRun = cloneGCData(gcData);
791
912
  this.tombstones = [];
792
913
  this.newReferencesSinceLastRun.clear();
793
914
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
@@ -823,27 +944,31 @@ export class GarbageCollector {
823
944
  }
824
945
  /**
825
946
  * Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
826
- * time. It's possible that nodes transition from `unreferenced -> referenced -> unreferenced` between two runs. The
827
- * unreferenced timestamp of such nodes needs to be reset as they may have been accessed when they were referenced.
947
+ * time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
948
+ * updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
949
+ * these objects while there can be in-memory referenced to it:
950
+ * 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
951
+ * added, the object may have been accessed and in-memory reference to it added.
952
+ * 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
953
+ * unreferenced, they could have been accessed and in-memory reference to them added.
828
954
  *
829
955
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
830
956
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
831
957
  */
832
958
  updateStateSinceLastRun(currentGCData, logger) {
833
959
  // If we haven't run GC before there is nothing to do.
834
- if (this.previousGCDataFromLastRun === undefined) {
960
+ if (this.gcDataFromLastRun === undefined) {
835
961
  return;
836
962
  }
837
963
  // Find any references that haven't been identified correctly.
838
- const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
964
+ const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
839
965
  if (missingExplicitReferences.length > 0) {
840
966
  missingExplicitReferences.forEach((missingExplicitReference) => {
841
- const event = {
967
+ logger.sendErrorEvent({
842
968
  eventName: "gcUnknownOutboundReferences",
843
969
  gcNodeId: missingExplicitReference[0],
844
970
  gcRoutes: JSON.stringify(missingExplicitReference[1]),
845
- };
846
- logger.sendPerformanceEvent(event);
971
+ });
847
972
  });
848
973
  }
849
974
  // No references were added since the last run so we don't have to update reference states of any unreferenced
@@ -857,21 +982,18 @@ export class GarbageCollector {
857
982
  * run, and then add the references since last run.
858
983
  *
859
984
  * Note on why we need to combine the data from previous run, current run and all references in between -
860
- *
861
985
  * 1. We need data from last run because some of its references may have been deleted since then. If those
862
- * references added new outbound references before getting deleted, we need to detect them.
986
+ * references added new outbound references before they were deleted, we need to detect them.
863
987
  *
864
988
  * 2. We need new outbound references since last run because some of them may have been deleted later. If those
865
- * references added new outbound references before getting deleted, we need to detect them.
989
+ * references added new outbound references before they were deleted, we need to detect them.
866
990
  *
867
991
  * 3. We need data from the current run because currently we may not detect when DDSes are referenced:
868
- *
869
- * - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
870
- * which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
871
- *
992
+ * - We don't require DDSes handles to be stored in a referenced DDS.
872
993
  * - A new data store may have "root" DDSes already created and we don't detect them today.
873
994
  */
874
- const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
995
+ const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
996
+ const newOutboundRoutesSinceLastRun = [];
875
997
  this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
876
998
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
877
999
  gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
@@ -879,19 +1001,25 @@ export class GarbageCollector {
879
1001
  else {
880
1002
  gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
881
1003
  }
1004
+ newOutboundRoutesSinceLastRun.push(...outboundRoutes);
882
1005
  });
883
1006
  /**
884
- * Run GC on the above reference graph to find all nodes that are referenced. For each one, if they are
1007
+ * Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
1008
+ * list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
885
1009
  * unreferenced, stop tracking them and remove from unreferenced list.
886
- * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
1010
+ * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
1011
+ * unreferenced and add unreferenced state.
887
1012
  */
888
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
1013
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
1014
+ "/",
1015
+ ...newOutboundRoutesSinceLastRun,
1016
+ ]);
889
1017
  for (const nodeId of gcResult.referencedNodeIds) {
890
1018
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
891
1019
  if (nodeStateTracker !== undefined) {
892
1020
  // Stop tracking so as to clear out any running timers.
893
1021
  nodeStateTracker.stopTracking();
894
- // Delete the node as we don't need to track it any more.
1022
+ // Delete the unreferenced state as we don't need to track it any more.
895
1023
  this.unreferencedNodesState.delete(nodeId);
896
1024
  }
897
1025
  }
@@ -910,7 +1038,7 @@ export class GarbageCollector {
910
1038
  * @returns - a list of missing explicit references
911
1039
  */
912
1040
  findMissingExplicitReferences(currentGCData, previousGCData, explicitReferences) {
913
- assert(previousGCData !== undefined, 0x2b7);
1041
+ assert(previousGCData !== undefined, 0x2b7 /* "Can't validate correctness without GC data from last run" */);
914
1042
  const currentGraph = Object.entries(currentGCData.gcNodes);
915
1043
  const missingExplicitReferences = [];
916
1044
  currentGraph.forEach(([nodeId, currentOutboundRoutes]) => {
@@ -918,14 +1046,20 @@ export class GarbageCollector {
918
1046
  const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
919
1047
  const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
920
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
+ */
921
1057
  currentOutboundRoutes.forEach((route) => {
922
- const isBlobOrDataStoreRoute = this.runtime.getNodeType(route) === GCNodeType.Blob ||
923
- this.runtime.getNodeType(route) === GCNodeType.DataStore;
924
- // Ignore implicitly added DDS routes to their parent datastores
925
- const notRouteFromDDSToParentDataStore = !nodeId.startsWith(route);
926
- if (isBlobOrDataStoreRoute &&
927
- notRouteFromDDSToParentDataStore &&
928
- (!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)) {
929
1063
  missingExplicitRoutes.push(route);
930
1064
  }
931
1065
  });
@@ -957,7 +1091,7 @@ export class GarbageCollector {
957
1091
  gcStats.nodeCount++;
958
1092
  // If there is no previous GC data, every node's state is generated and is considered as updated.
959
1093
  // Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
960
- const stateUpdated = this.previousGCDataFromLastRun === undefined ||
1094
+ const stateUpdated = this.gcDataFromLastRun === undefined ||
961
1095
  this.unreferencedNodesState.has(nodeId) === referenced;
962
1096
  if (stateUpdated) {
963
1097
  gcStats.updatedNodeCount++;
@@ -997,7 +1131,8 @@ export class GarbageCollector {
997
1131
  * this will give us a view into how much deleted content a container has.
998
1132
  */
999
1133
  logSweepEvents(logger, currentReferenceTimestampMs) {
1000
- if (this.mc.config.getBoolean(disableSweepLogKey) === true || this.sweepTimeoutMs === undefined) {
1134
+ if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
1135
+ this.sweepTimeoutMs === undefined) {
1001
1136
  return;
1002
1137
  }
1003
1138
  this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
@@ -1032,7 +1167,8 @@ export class GarbageCollector {
1032
1167
  // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
1033
1168
  // logging as nothing interesting would have happened worth logging.
1034
1169
  // If the node is active, skip logging.
1035
- if (currentReferenceTimestampMs === undefined || nodeStateTracker.state === UnreferencedState.Active) {
1170
+ if (currentReferenceTimestampMs === undefined ||
1171
+ nodeStateTracker.state === UnreferencedState.Active) {
1036
1172
  return;
1037
1173
  }
1038
1174
  // We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
@@ -1064,9 +1200,17 @@ export class GarbageCollector {
1064
1200
  // Events generated:
1065
1201
  // InactiveObject_Loaded, SweepReadyObject_Loaded
1066
1202
  if (usageType === "Loaded") {
1067
- 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
+ }
1068
1212
  }
1069
- // 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.
1070
1214
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
1071
1215
  // and errors will arise elsewhere in the runtime
1072
1216
  if (state === UnreferencedState.SweepReady) {
@@ -1089,43 +1233,29 @@ export class GarbageCollector {
1089
1233
  * revived and a Revived event will be logged for it.
1090
1234
  */
1091
1235
  const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
1092
- const active = nodeStateTracker === undefined || nodeStateTracker.state === UnreferencedState.Active;
1236
+ const active = nodeStateTracker === undefined ||
1237
+ nodeStateTracker.state === UnreferencedState.Active;
1093
1238
  if ((usageType === "Revived") === active) {
1094
1239
  const pkg = await this.getNodePackagePath(eventProps.id);
1095
- const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
1096
- 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
+ }
1097
1254
  }
1098
1255
  }
1099
1256
  this.pendingEventsQueue = [];
1100
1257
  }
1101
1258
  }
1102
- /**
1103
- * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1104
- * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1105
- */
1106
- async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1107
- let rootGCState = { gcNodes: {} };
1108
- let tombstones;
1109
- for (const key of Object.keys(gcSnapshotTree.blobs)) {
1110
- if (key === gcTombstoneBlobKey) {
1111
- tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1112
- continue;
1113
- }
1114
- // Skip blobs that do not start with the GC prefix.
1115
- if (!key.startsWith(gcBlobPrefix)) {
1116
- continue;
1117
- }
1118
- const blobId = gcSnapshotTree.blobs[key];
1119
- if (blobId === undefined) {
1120
- continue;
1121
- }
1122
- const gcState = await readAndParseBlob(blobId);
1123
- assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
1124
- // Merge the GC state of this blob into the root GC state.
1125
- rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1126
- }
1127
- return { gcState: rootGCState, tombstones };
1128
- }
1129
1259
  function generateSortedGCState(gcState) {
1130
1260
  const sortableArray = Object.entries(gcState.gcNodes);
1131
1261
  sortableArray.sort(([a], [b]) => a.localeCompare(b));
@@ -1140,7 +1270,9 @@ function generateSortedGCState(gcState) {
1140
1270
  class TimerWithNoDefaultTimeout extends Timer {
1141
1271
  constructor(callback) {
1142
1272
  // The default timeout/handlers will never be used since start/restart pass overrides below
1143
- super(0, () => { throw new Error("DefaultHandler should not be used"); });
1273
+ super(0, () => {
1274
+ throw new Error("DefaultHandler should not be used");
1275
+ });
1144
1276
  this.callback = callback;
1145
1277
  }
1146
1278
  start(timeoutMs) {