@fluidframework/container-runtime 2.0.0-dev.7.4.0.217884 → 2.0.0-dev.7.4.0.221926

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 (213) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/api-extractor.json +0 -3
  3. package/api-report/container-runtime.api.md +22 -18
  4. package/dist/blobManager.d.ts +3 -3
  5. package/dist/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager.js.map +1 -1
  7. package/dist/container-runtime-alpha.d.ts +42 -21
  8. package/dist/container-runtime-beta.d.ts +40 -2
  9. package/dist/container-runtime-public.d.ts +40 -2
  10. package/dist/container-runtime-untrimmed.d.ts +51 -38
  11. package/dist/containerRuntime.d.ts +9 -7
  12. package/dist/containerRuntime.d.ts.map +1 -1
  13. package/dist/containerRuntime.js +42 -22
  14. package/dist/containerRuntime.js.map +1 -1
  15. package/dist/dataStores.d.ts +10 -15
  16. package/dist/dataStores.d.ts.map +1 -1
  17. package/dist/dataStores.js +63 -36
  18. package/dist/dataStores.js.map +1 -1
  19. package/dist/gc/garbageCollection.d.ts +29 -10
  20. package/dist/gc/garbageCollection.d.ts.map +1 -1
  21. package/dist/gc/garbageCollection.js +149 -67
  22. package/dist/gc/garbageCollection.js.map +1 -1
  23. package/dist/gc/gcConfigs.d.ts.map +1 -1
  24. package/dist/gc/gcConfigs.js +34 -37
  25. package/dist/gc/gcConfigs.js.map +1 -1
  26. package/dist/gc/gcDefinitions.d.ts +88 -35
  27. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  28. package/dist/gc/gcDefinitions.js +25 -15
  29. package/dist/gc/gcDefinitions.js.map +1 -1
  30. package/dist/gc/gcHelpers.d.ts +18 -25
  31. package/dist/gc/gcHelpers.d.ts.map +1 -1
  32. package/dist/gc/gcHelpers.js +29 -45
  33. package/dist/gc/gcHelpers.js.map +1 -1
  34. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  35. package/dist/gc/gcTelemetry.js +14 -3
  36. package/dist/gc/gcTelemetry.js.map +1 -1
  37. package/dist/gc/gcUnreferencedStateTracker.d.ts +11 -5
  38. package/dist/gc/gcUnreferencedStateTracker.d.ts.map +1 -1
  39. package/dist/gc/gcUnreferencedStateTracker.js +43 -19
  40. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  41. package/dist/gc/index.d.ts +1 -1
  42. package/dist/gc/index.d.ts.map +1 -1
  43. package/dist/gc/index.js +4 -4
  44. package/dist/gc/index.js.map +1 -1
  45. package/dist/index.d.ts +13 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +16 -5
  48. package/dist/index.js.map +1 -1
  49. package/dist/messageTypes.d.ts +13 -5
  50. package/dist/messageTypes.d.ts.map +1 -1
  51. package/dist/messageTypes.js +5 -0
  52. package/dist/messageTypes.js.map +1 -1
  53. package/dist/packageVersion.d.ts +1 -1
  54. package/dist/packageVersion.js +1 -1
  55. package/dist/packageVersion.js.map +1 -1
  56. package/dist/pendingStateManager.d.ts +1 -0
  57. package/dist/pendingStateManager.d.ts.map +1 -1
  58. package/dist/pendingStateManager.js +1 -0
  59. package/dist/pendingStateManager.js.map +1 -1
  60. package/lib/blobManager.d.ts +3 -3
  61. package/lib/blobManager.d.ts.map +1 -1
  62. package/lib/blobManager.js.map +1 -1
  63. package/lib/container-runtime-alpha.d.ts +42 -21
  64. package/lib/container-runtime-beta.d.ts +40 -2
  65. package/lib/container-runtime-public.d.ts +40 -2
  66. package/lib/container-runtime-untrimmed.d.ts +51 -38
  67. package/lib/containerRuntime.d.ts +9 -7
  68. package/lib/containerRuntime.d.ts.map +1 -1
  69. package/lib/containerRuntime.js +44 -24
  70. package/lib/containerRuntime.js.map +1 -1
  71. package/lib/dataStores.d.ts +10 -15
  72. package/lib/dataStores.d.ts.map +1 -1
  73. package/lib/dataStores.js +65 -38
  74. package/lib/dataStores.js.map +1 -1
  75. package/lib/gc/garbageCollection.d.ts +29 -10
  76. package/lib/gc/garbageCollection.d.ts.map +1 -1
  77. package/lib/gc/garbageCollection.js +151 -69
  78. package/lib/gc/garbageCollection.js.map +1 -1
  79. package/lib/gc/gcConfigs.d.ts.map +1 -1
  80. package/lib/gc/gcConfigs.js +37 -40
  81. package/lib/gc/gcConfigs.js.map +1 -1
  82. package/lib/gc/gcDefinitions.d.ts +88 -35
  83. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  84. package/lib/gc/gcDefinitions.js +24 -14
  85. package/lib/gc/gcDefinitions.js.map +1 -1
  86. package/lib/gc/gcHelpers.d.ts +18 -25
  87. package/lib/gc/gcHelpers.d.ts.map +1 -1
  88. package/lib/gc/gcHelpers.js +27 -43
  89. package/lib/gc/gcHelpers.js.map +1 -1
  90. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  91. package/lib/gc/gcTelemetry.js +14 -3
  92. package/lib/gc/gcTelemetry.js.map +1 -1
  93. package/lib/gc/gcUnreferencedStateTracker.d.ts +11 -5
  94. package/lib/gc/gcUnreferencedStateTracker.d.ts.map +1 -1
  95. package/lib/gc/gcUnreferencedStateTracker.js +43 -19
  96. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  97. package/lib/gc/index.d.ts +1 -1
  98. package/lib/gc/index.d.ts.map +1 -1
  99. package/lib/gc/index.js +1 -1
  100. package/lib/gc/index.js.map +1 -1
  101. package/lib/index.d.ts +13 -1
  102. package/lib/index.d.ts.map +1 -1
  103. package/lib/index.js +15 -1
  104. package/lib/index.js.map +1 -1
  105. package/lib/messageTypes.d.ts +13 -5
  106. package/lib/messageTypes.d.ts.map +1 -1
  107. package/lib/messageTypes.js +5 -0
  108. package/lib/messageTypes.js.map +1 -1
  109. package/lib/packageVersion.d.ts +1 -1
  110. package/lib/packageVersion.js +1 -1
  111. package/lib/packageVersion.js.map +1 -1
  112. package/lib/pendingStateManager.d.ts +1 -0
  113. package/lib/pendingStateManager.d.ts.map +1 -1
  114. package/lib/pendingStateManager.js +1 -0
  115. package/lib/pendingStateManager.js.map +1 -1
  116. package/package.json +18 -15
  117. package/src/blobManager.ts +4 -4
  118. package/src/containerRuntime.ts +56 -30
  119. package/src/dataStores.ts +118 -59
  120. package/src/gc/garbageCollection.md +14 -15
  121. package/src/gc/garbageCollection.ts +182 -75
  122. package/src/gc/gcConfigs.ts +50 -52
  123. package/src/gc/gcDefinitions.ts +103 -41
  124. package/src/gc/gcHelpers.ts +31 -52
  125. package/src/gc/gcTelemetry.ts +16 -4
  126. package/src/gc/gcUnreferencedStateTracker.ts +61 -22
  127. package/src/gc/index.ts +4 -3
  128. package/src/index.ts +17 -1
  129. package/src/messageTypes.ts +16 -2
  130. package/src/packageVersion.ts +1 -1
  131. package/src/pendingStateManager.ts +1 -0
  132. package/dist/id-compressor/appendOnlySortedMap.d.ts +0 -124
  133. package/dist/id-compressor/appendOnlySortedMap.d.ts.map +0 -1
  134. package/dist/id-compressor/appendOnlySortedMap.js +0 -318
  135. package/dist/id-compressor/appendOnlySortedMap.js.map +0 -1
  136. package/dist/id-compressor/finalSpace.d.ts +0 -29
  137. package/dist/id-compressor/finalSpace.d.ts.map +0 -1
  138. package/dist/id-compressor/finalSpace.js +0 -62
  139. package/dist/id-compressor/finalSpace.js.map +0 -1
  140. package/dist/id-compressor/idCompressor.d.ts +0 -54
  141. package/dist/id-compressor/idCompressor.d.ts.map +0 -1
  142. package/dist/id-compressor/idCompressor.js +0 -495
  143. package/dist/id-compressor/idCompressor.js.map +0 -1
  144. package/dist/id-compressor/identifiers.d.ts +0 -32
  145. package/dist/id-compressor/identifiers.d.ts.map +0 -1
  146. package/dist/id-compressor/identifiers.js +0 -15
  147. package/dist/id-compressor/identifiers.js.map +0 -1
  148. package/dist/id-compressor/index.d.ts +0 -13
  149. package/dist/id-compressor/index.d.ts.map +0 -1
  150. package/dist/id-compressor/index.js +0 -32
  151. package/dist/id-compressor/index.js.map +0 -1
  152. package/dist/id-compressor/persistanceUtilities.d.ts +0 -22
  153. package/dist/id-compressor/persistanceUtilities.d.ts.map +0 -1
  154. package/dist/id-compressor/persistanceUtilities.js +0 -43
  155. package/dist/id-compressor/persistanceUtilities.js.map +0 -1
  156. package/dist/id-compressor/sessionSpaceNormalizer.d.ts +0 -46
  157. package/dist/id-compressor/sessionSpaceNormalizer.d.ts.map +0 -1
  158. package/dist/id-compressor/sessionSpaceNormalizer.js +0 -80
  159. package/dist/id-compressor/sessionSpaceNormalizer.js.map +0 -1
  160. package/dist/id-compressor/sessions.d.ts +0 -115
  161. package/dist/id-compressor/sessions.d.ts.map +0 -1
  162. package/dist/id-compressor/sessions.js +0 -305
  163. package/dist/id-compressor/sessions.js.map +0 -1
  164. package/dist/id-compressor/utilities.d.ts +0 -52
  165. package/dist/id-compressor/utilities.d.ts.map +0 -1
  166. package/dist/id-compressor/utilities.js +0 -169
  167. package/dist/id-compressor/utilities.js.map +0 -1
  168. package/lib/id-compressor/appendOnlySortedMap.d.ts +0 -124
  169. package/lib/id-compressor/appendOnlySortedMap.d.ts.map +0 -1
  170. package/lib/id-compressor/appendOnlySortedMap.js +0 -314
  171. package/lib/id-compressor/appendOnlySortedMap.js.map +0 -1
  172. package/lib/id-compressor/finalSpace.d.ts +0 -29
  173. package/lib/id-compressor/finalSpace.d.ts.map +0 -1
  174. package/lib/id-compressor/finalSpace.js +0 -58
  175. package/lib/id-compressor/finalSpace.js.map +0 -1
  176. package/lib/id-compressor/idCompressor.d.ts +0 -54
  177. package/lib/id-compressor/idCompressor.d.ts.map +0 -1
  178. package/lib/id-compressor/idCompressor.js +0 -491
  179. package/lib/id-compressor/idCompressor.js.map +0 -1
  180. package/lib/id-compressor/identifiers.d.ts +0 -32
  181. package/lib/id-compressor/identifiers.d.ts.map +0 -1
  182. package/lib/id-compressor/identifiers.js +0 -11
  183. package/lib/id-compressor/identifiers.js.map +0 -1
  184. package/lib/id-compressor/index.d.ts +0 -13
  185. package/lib/id-compressor/index.d.ts.map +0 -1
  186. package/lib/id-compressor/index.js +0 -13
  187. package/lib/id-compressor/index.js.map +0 -1
  188. package/lib/id-compressor/persistanceUtilities.d.ts +0 -22
  189. package/lib/id-compressor/persistanceUtilities.d.ts.map +0 -1
  190. package/lib/id-compressor/persistanceUtilities.js +0 -34
  191. package/lib/id-compressor/persistanceUtilities.js.map +0 -1
  192. package/lib/id-compressor/sessionSpaceNormalizer.d.ts +0 -46
  193. package/lib/id-compressor/sessionSpaceNormalizer.d.ts.map +0 -1
  194. package/lib/id-compressor/sessionSpaceNormalizer.js +0 -76
  195. package/lib/id-compressor/sessionSpaceNormalizer.js.map +0 -1
  196. package/lib/id-compressor/sessions.d.ts +0 -115
  197. package/lib/id-compressor/sessions.d.ts.map +0 -1
  198. package/lib/id-compressor/sessions.js +0 -290
  199. package/lib/id-compressor/sessions.js.map +0 -1
  200. package/lib/id-compressor/utilities.d.ts +0 -52
  201. package/lib/id-compressor/utilities.d.ts.map +0 -1
  202. package/lib/id-compressor/utilities.js +0 -151
  203. package/lib/id-compressor/utilities.js.map +0 -1
  204. package/src/id-compressor/README.md +0 -69
  205. package/src/id-compressor/appendOnlySortedMap.ts +0 -366
  206. package/src/id-compressor/finalSpace.ts +0 -67
  207. package/src/id-compressor/idCompressor.ts +0 -630
  208. package/src/id-compressor/identifiers.ts +0 -42
  209. package/src/id-compressor/index.ts +0 -26
  210. package/src/id-compressor/persistanceUtilities.ts +0 -58
  211. package/src/id-compressor/sessionSpaceNormalizer.ts +0 -83
  212. package/src/id-compressor/sessions.ts +0 -405
  213. package/src/id-compressor/utilities.ts +0 -190
@@ -21,13 +21,13 @@ import {
21
21
  MonitoringContext,
22
22
  PerformanceEvent,
23
23
  } from "@fluidframework/telemetry-utils";
24
-
25
24
  import {
26
25
  InactiveResponseHeaderKey,
27
26
  RuntimeHeaderData,
28
27
  TombstoneResponseHeaderKey,
29
28
  } from "../containerRuntime";
30
29
  import { ClientSessionExpiredError } from "../error";
30
+ import { ContainerMessageType, ContainerRuntimeGCMessage } from "../messageTypes";
31
31
  import { IRefreshSummaryResult } from "../summary";
32
32
  import { generateGCConfigs } from "./gcConfigs";
33
33
  import {
@@ -42,8 +42,15 @@ import {
42
42
  IGarbageCollectorConfigs,
43
43
  IMarkPhaseStats,
44
44
  ISweepPhaseStats,
45
+ GarbageCollectionMessage,
46
+ GarbageCollectionMessageType,
45
47
  } from "./gcDefinitions";
46
- import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot } from "./gcHelpers";
48
+ import {
49
+ cloneGCData,
50
+ compatBehaviorAllowsGCMessageType,
51
+ concatGarbageCollectionData,
52
+ getGCDataFromSnapshot,
53
+ } from "./gcHelpers";
47
54
  import { runGarbageCollection } from "./gcReferenceGraphAlgorithm";
48
55
  import { IGarbageCollectionSnapshotData, IGarbageCollectionState } from "./gcSummaryDefinitions";
49
56
  import { GCSummaryStateTracker } from "./gcSummaryStateTracker";
@@ -117,7 +124,7 @@ export class GarbageCollector implements IGarbageCollector {
117
124
 
118
125
  /** If false, loading or using a Tombstoned object should merely log, not fail */
119
126
  public get tombstoneEnforcementAllowed(): boolean {
120
- return this.configs.tombstoneEnforcementAllowed;
127
+ return this.configs.sweepEnabled;
121
128
  }
122
129
  /** If true, throw an error when a tombstone data store is retrieved */
123
130
  public get throwOnTombstoneLoad(): boolean {
@@ -137,6 +144,8 @@ export class GarbageCollector implements IGarbageCollector {
137
144
  /** Returns true if connection is active, i.e. it's "write" connection and the runtime is connected. */
138
145
  private readonly activeConnection: () => boolean;
139
146
 
147
+ private readonly submitMessage: (message: ContainerRuntimeGCMessage) => void;
148
+
140
149
  public get summaryStateNeedsReset(): boolean {
141
150
  return this.summaryStateTracker.doesSummaryStateNeedReset;
142
151
  }
@@ -152,6 +161,7 @@ export class GarbageCollector implements IGarbageCollector {
152
161
  this.getNodePackagePath = createParams.getNodePackagePath;
153
162
  this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
154
163
  this.activeConnection = createParams.activeConnection;
164
+ this.submitMessage = createParams.submitMessage;
155
165
 
156
166
  const baseSnapshot = createParams.baseSnapshot;
157
167
  const readAndParseBlob = createParams.readAndParseBlob;
@@ -247,7 +257,7 @@ export class GarbageCollector implements IGarbageCollector {
247
257
  /**
248
258
  * Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
249
259
  * connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
250
- * GC state and updates their inactive or sweep ready state.
260
+ * GC state and updates their inactive or sweep-ready state.
251
261
  */
252
262
  this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
253
263
  /**
@@ -418,6 +428,7 @@ export class GarbageCollector implements IGarbageCollector {
418
428
  this.configs.inactiveTimeoutMs,
419
429
  currentReferenceTimestampMs,
420
430
  this.configs.sweepTimeoutMs,
431
+ this.configs.sweepGracePeriodMs,
421
432
  ),
422
433
  );
423
434
  }
@@ -428,7 +439,7 @@ export class GarbageCollector implements IGarbageCollector {
428
439
 
429
440
  /**
430
441
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
431
- * to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
442
+ * to initialize the base state for non-summarizer clients so that they can track inactive / sweep-ready nodes.
432
443
  * @param connected - Whether the runtime connected / disconnected.
433
444
  * @param clientId - The clientId of this runtime.
434
445
  */
@@ -444,7 +455,7 @@ export class GarbageCollector implements IGarbageCollector {
444
455
  * the receiving summarizer client.
445
456
  *
446
457
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
447
- * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
458
+ * sweep in phases and we want to track when inactive and sweep-ready objects are used in any client.
448
459
  */
449
460
  if (this.activeConnection() && this.configs.shouldRunGC) {
450
461
  this.initializeGCStateFromBaseSnapshotP.catch((error) => {});
@@ -547,10 +558,15 @@ export class GarbageCollector implements IGarbageCollector {
547
558
 
548
559
  /**
549
560
  * Runs garbage collection. It does the following:
561
+ *
550
562
  * 1. It generates / analyzes the runtime's reference graph.
563
+ *
551
564
  * 2. Generates mark phase stats.
565
+ *
552
566
  * 3. Runs Mark phase.
567
+ *
553
568
  * 4. Runs Sweep phase.
569
+ *
554
570
  * 5. Generates sweep phase stats.
555
571
  */
556
572
  private async runGC(
@@ -572,26 +588,24 @@ export class GarbageCollector implements IGarbageCollector {
572
588
  const markPhaseStats = this.getMarkPhaseStats(gcResult);
573
589
 
574
590
  // 3. Run the Mark phase.
575
- // It will mark nodes as referenced / unreferenced and return a list of node ids that are ready to be swept.
576
- const sweepReadyNodeIds = this.runMarkPhase(
591
+ // It will mark nodes as referenced / unreferenced and return lists of tombstone-ready and sweep-ready nodes.
592
+ const { tombstoneReadyNodeIds, sweepReadyNodeIds } = this.runMarkPhase(
577
593
  gcResult,
578
594
  allReferencedNodeIds,
579
595
  currentReferenceTimestampMs,
580
596
  );
581
597
 
582
598
  // 4. Run the Sweep phase.
583
- // It will delete sweep ready nodes and return a list of deleted node ids.
584
- const deletedNodeIds = this.runSweepPhase(gcResult, sweepReadyNodeIds);
599
+ // It will tombstone any tombstone-ready nodes, and initiate the deletion of sweep-ready nodes by sending a
600
+ // sweep op. All clients, including this one, will delete these nodes once it processes the op.
601
+ this.runSweepPhase(gcResult, tombstoneReadyNodeIds, sweepReadyNodeIds);
585
602
 
586
- this.gcDataFromLastRun = cloneGCData(
587
- gcData,
588
- (id: string) => deletedNodeIds.includes(id) /* filter out deleted nodes */,
589
- );
603
+ this.gcDataFromLastRun = cloneGCData(gcData);
590
604
 
591
605
  // 5. Get the sweep phase stats.
592
606
  const sweepPhaseStats = this.getSweepPhaseStats(
593
- this.configs.shouldRunSweep ? this.deletedNodes : sweepReadyNodeIds,
594
- new Set(deletedNodeIds),
607
+ this.deletedNodes,
608
+ sweepReadyNodeIds,
595
609
  markPhaseStats,
596
610
  );
597
611
 
@@ -610,13 +624,13 @@ export class GarbageCollector implements IGarbageCollector {
610
624
  * @param gcResult - The result of the GC run on the gcData.
611
625
  * @param allReferencedNodeIds - Nodes referenced in this GC run + referenced between previous and current GC run.
612
626
  * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
613
- * @returns A list of sweep ready nodes, i.e., nodes that ready to be deleted.
627
+ * @returns The sets of tombstone-ready and sweep-ready nodes, i.e., nodes that ready to be tombstoned or deleted.
614
628
  */
615
629
  private runMarkPhase(
616
630
  gcResult: IGCResult,
617
631
  allReferencedNodeIds: string[],
618
632
  currentReferenceTimestampMs: number,
619
- ): Set<string> {
633
+ ): { tombstoneReadyNodeIds: Set<string>; sweepReadyNodeIds: Set<string> } {
620
634
  // 1. Marks all referenced nodes by clearing their unreferenced tracker, if any.
621
635
  for (const nodeId of allReferencedNodeIds) {
622
636
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
@@ -629,6 +643,7 @@ export class GarbageCollector implements IGarbageCollector {
629
643
  }
630
644
 
631
645
  // 2. Mark unreferenced nodes in this run by starting unreferenced tracking for them.
646
+ const tombstoneReadyNodeIds: Set<string> = new Set();
632
647
  const sweepReadyNodeIds: Set<string> = new Set();
633
648
  for (const nodeId of gcResult.deletedNodeIds) {
634
649
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
@@ -640,6 +655,7 @@ export class GarbageCollector implements IGarbageCollector {
640
655
  this.configs.inactiveTimeoutMs,
641
656
  currentReferenceTimestampMs,
642
657
  this.configs.sweepTimeoutMs,
658
+ this.configs.sweepGracePeriodMs,
643
659
  ),
644
660
  );
645
661
  } else {
@@ -647,7 +663,10 @@ export class GarbageCollector implements IGarbageCollector {
647
663
  // is from the ops seen, this will ensure that we keep updating unreferenced state as time moves forward.
648
664
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
649
665
 
650
- // If a node is sweep ready, store it so it can be returned.
666
+ // If a node is tombstone or sweep-ready, store it so it can be returned.
667
+ if (nodeStateTracker.state === UnreferencedState.TombstoneReady) {
668
+ tombstoneReadyNodeIds.add(nodeId);
669
+ }
651
670
  if (nodeStateTracker.state === UnreferencedState.SweepReady) {
652
671
  sweepReadyNodeIds.add(nodeId);
653
672
  }
@@ -657,67 +676,82 @@ export class GarbageCollector implements IGarbageCollector {
657
676
  // 3. Call the runtime to update referenced nodes in this run.
658
677
  this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
659
678
 
660
- return sweepReadyNodeIds;
679
+ return { tombstoneReadyNodeIds, sweepReadyNodeIds };
661
680
  }
662
681
 
663
682
  /**
664
683
  * Runs the GC Sweep phase. It does the following:
665
- * 1. Calls the runtime to delete nodes that are sweep ready.
666
- * 2. Clears tracking for deleted nodes.
684
+ *
685
+ * 1. Marks tombstone-ready nodes as tombstones.
686
+ *
687
+ * 2. Sends a sweep op to delete nodes that are sweep-ready. Once the op is ack'd, these nodes will be deleted.
667
688
  *
668
689
  * @param gcResult - The result of the GC run on the gcData.
669
- * @param sweepReadyNodes - List of nodes that are sweep ready.
670
- * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
671
- * @param logger - The logger to be used to log any telemetry.
672
- * @returns A list of nodes that have been deleted.
690
+ * @param tombstoneReadyNodes - List of nodes that are tombstone-ready.
691
+ * @param sweepReadyNodes - List of nodes that are sweep-ready.
673
692
  */
674
- private runSweepPhase(gcResult: IGCResult, sweepReadyNodes: Set<string>): string[] {
693
+ private runSweepPhase(
694
+ gcResult: IGCResult,
695
+ tombstoneReadyNodes: Set<string>,
696
+ sweepReadyNodes: Set<string>,
697
+ ) {
675
698
  /**
676
- * Currently, there are 3 modes for sweep:
677
- * Test mode - Unreferenced nodes are immediately deleted without waiting for them to be sweep ready.
678
- * Tombstone mode - Sweep ready modes are marked as tombstones instead of being deleted.
679
- * Sweep mode - Sweep ready modes are deleted.
699
+ * Under "Test Mode", unreferenced nodes are immediately deleted without waiting for them to be sweep-ready.
680
700
  *
681
- * These modes serve as staging for applications that want to enable sweep by providing an incremental
682
- * way to test and validate sweep works as expected.
701
+ * Otherwise, depending on how long it's been since the node was unreferenced, it will either be
702
+ * marked as Tombstone, or deleted by Sweep.
683
703
  */
704
+
684
705
  if (this.configs.testMode) {
685
706
  // If we are running in GC test mode, unreferenced nodes (gcResult.deletedNodeIds) are deleted.
686
707
  this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
687
- return [];
708
+ return;
688
709
  }
689
710
 
711
+ // If sweep is disabled, we'll tombstone both tombstone-ready and sweep-ready nodes.
712
+ // This is important because a container may never load during a node's Sweep Grace Period,
713
+ // so that node would directly become sweep-ready skipping over tombstone-ready state,
714
+ // but should be Tombstoned since Sweep is disabled.
715
+ const { nodesToTombstone, nodesToDelete } = this.configs.shouldRunSweep
716
+ ? {
717
+ nodesToTombstone: [...tombstoneReadyNodes],
718
+ nodesToDelete: [...sweepReadyNodes],
719
+ }
720
+ : {
721
+ nodesToTombstone: [...tombstoneReadyNodes, ...sweepReadyNodes],
722
+ nodesToDelete: [],
723
+ };
724
+
690
725
  if (this.configs.tombstoneMode) {
691
- this.tombstones = Array.from(sweepReadyNodes);
692
- // If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
693
- // involving access to "deleted" data without actually deleting the data from summaries.
726
+ this.tombstones = nodesToTombstone;
727
+ // If we are running in GC tombstone mode, update tombstoned routes.
694
728
  this.runtime.updateTombstonedRoutes(this.tombstones);
695
- return [];
696
- }
697
-
698
- if (!this.configs.shouldRunSweep) {
699
- return [];
700
729
  }
701
730
 
702
- // 1. Call the runtime to delete sweep ready nodes. The runtime returns a list of nodes it deleted.
703
- // TODO: GC:Validation - validate that removed routes are not double delete and that the child routes of
704
- // removed routes are deleted as well.
705
- const deletedNodeIds = this.runtime.deleteSweepReadyNodes(Array.from(sweepReadyNodes));
731
+ if (this.configs.shouldRunSweep && nodesToDelete.length > 0) {
732
+ // Do not send DDS node ids in the GC op. This is an optimization to reduce its size. Since GC applies to
733
+ // to data store only, all its DDSes are deleted along with it. The DDS ids will be retrieved from the
734
+ // local state when processing the op.
735
+ const sweepReadyDSAndBlobs = nodesToDelete.filter((nodeId) => {
736
+ const nodeType = this.runtime.getNodeType(nodeId);
737
+ return nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob;
738
+ });
739
+ const contents: GarbageCollectionMessage = {
740
+ type: GarbageCollectionMessageType.Sweep,
741
+ deletedNodeIds: sweepReadyDSAndBlobs,
742
+ };
706
743
 
707
- // 2. Clear unreferenced state tracking for deleted nodes.
708
- for (const nodeId of deletedNodeIds) {
709
- const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
710
- // TODO: GC:Validation - assert that the nodeStateTracker is defined
711
- if (nodeStateTracker !== undefined) {
712
- // Stop tracking so as to clear out any running timers.
713
- nodeStateTracker.stopTracking();
714
- // Delete the node as we don't need to track it any more.
715
- this.unreferencedNodesState.delete(nodeId);
716
- }
717
- // TODO: GC:Validation - assert that the deleted node is not a duplicate
718
- this.deletedNodes.add(nodeId);
744
+ // Its fine for older clients to ignore this op because it doesn't have any functional impact. This op
745
+ // is an optimization to ensure that all clients are in sync when it comes to deleted nodes to prevent their
746
+ // accidental usage. The clients will sync without the delete op too but it may take longer.
747
+ const containerGCMessage: ContainerRuntimeGCMessage = {
748
+ type: ContainerMessageType.GC,
749
+ contents,
750
+ compatDetails: { behavior: "Ignore" },
751
+ };
752
+ this.submitMessage(containerGCMessage);
753
+ return;
719
754
  }
720
- return deletedNodeIds;
721
755
  }
722
756
 
723
757
  /**
@@ -857,6 +891,78 @@ export class GarbageCollector implements IGarbageCollector {
857
891
  return this.summaryStateTracker.refreshLatestSummary(result);
858
892
  }
859
893
 
894
+ /**
895
+ * Process a GC message.
896
+ * @param message - The GC message from the container runtime.
897
+ * @param local - Whether it was send by this client.
898
+ */
899
+ public processMessage(message: ContainerRuntimeGCMessage, local: boolean) {
900
+ switch (message.contents.type) {
901
+ case "Sweep": {
902
+ // Delete the nodes whose ids are present in the contents.
903
+ this.deleteSweepReadyNodes(message.contents.deletedNodeIds);
904
+ break;
905
+ }
906
+ default: {
907
+ if (
908
+ !compatBehaviorAllowsGCMessageType(
909
+ message.contents.type,
910
+ message.compatDetails?.behavior,
911
+ )
912
+ ) {
913
+ const error = DataProcessingError.create(
914
+ `Garbage collection message of unknown type ${message.contents.type}`,
915
+ "processMessage",
916
+ );
917
+ throw error;
918
+ }
919
+ break;
920
+ }
921
+ }
922
+ }
923
+
924
+ /**
925
+ * Delete nodes that are sweep-ready. Call the runtime to delete these nodes and clear the unreferenced state
926
+ * tracking for nodes that are actually deleted by the runtime.
927
+ * @param sweepReadyNodeIds - The ids of nodes that are ready to be deleted.
928
+ */
929
+ private deleteSweepReadyNodes(sweepReadyNodeIds: readonly string[]) {
930
+ // Use a set for lookup because its much faster than array or map.
931
+ const sweepReadyNodesSet: Set<string> = new Set(sweepReadyNodeIds);
932
+
933
+ // The ids in the sweep-ready nodes do not contain DDS node ids. This is an optimization to reduce the size
934
+ // of the GC op. Since GC applies to data store only, all its DDSes are deleted along with it. So, get the
935
+ // DDS nodes ID from the unreferenced nodes state.
936
+ const allSweepReadyNodeIds = Array.from(sweepReadyNodeIds);
937
+ for (const [id] of this.unreferencedNodesState) {
938
+ // Ignore data store nodes since they would already be in the list.
939
+ const pathParts = id.split("/");
940
+ if (pathParts.length <= 2) {
941
+ continue;
942
+ }
943
+
944
+ // Get the data store id part. Note that this may include blobs but that's okay since the part would just
945
+ // be "_blobs" and it won't be found.
946
+ const dsId = `/${pathParts[1]}`;
947
+ if (sweepReadyNodesSet.has(dsId)) {
948
+ allSweepReadyNodeIds.push(id);
949
+ }
950
+ }
951
+ const deletedNodeIds = this.runtime.deleteSweepReadyNodes(allSweepReadyNodeIds);
952
+
953
+ // Clear unreferenced state tracking for deleted nodes.
954
+ for (const nodeId of deletedNodeIds) {
955
+ const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
956
+ if (nodeStateTracker !== undefined) {
957
+ // Stop tracking so as to clear out any running timers.
958
+ nodeStateTracker.stopTracking();
959
+ // Delete the node as we don't need to track it any more.
960
+ this.unreferencedNodesState.delete(nodeId);
961
+ }
962
+ this.deletedNodes.add(nodeId);
963
+ }
964
+ }
965
+
860
966
  /**
861
967
  * Called when a node with the given id is updated. If the node is inactive or tombstoned, this will log an error
862
968
  * or throw an error if failing on incorrect usage is configured.
@@ -1036,14 +1142,14 @@ export class GarbageCollector implements IGarbageCollector {
1036
1142
 
1037
1143
  /**
1038
1144
  * Generates the stats of a garbage collection sweep phase run.
1039
- * @param allDeletedNodes - All the nodes that have been deleted across all GC runs.
1040
- * @param currentDeletedNodes - The nodes that have been deleted in this GC run.
1145
+ * @param deletedNodes - The nodes that have been deleted until this run.
1146
+ * @param sweepReadyNodes - The nodes that are sweep-ready in this GC run.
1041
1147
  * @param markPhaseStats - The stats of the mark phase run.
1042
1148
  * @returns the stats of the sweep phase run.
1043
1149
  */
1044
1150
  private getSweepPhaseStats(
1045
- allDeletedNodes: Set<string>,
1046
- currentDeletedNodes: Set<string>,
1151
+ deletedNodes: Set<string>,
1152
+ sweepReadyNodes: Set<string>,
1047
1153
  markPhaseStats: IMarkPhaseStats,
1048
1154
  ): ISweepPhaseStats {
1049
1155
  // Initialize the life time node counts to the mark phase node counts. If sweep is not enabled,
@@ -1057,7 +1163,7 @@ export class GarbageCollector implements IGarbageCollector {
1057
1163
  deletedAttachmentBlobCount: 0,
1058
1164
  };
1059
1165
 
1060
- for (const nodeId of allDeletedNodes) {
1166
+ for (const nodeId of deletedNodes) {
1061
1167
  sweepPhaseStats.deletedNodeCount++;
1062
1168
  const nodeType = this.runtime.getNodeType(nodeId);
1063
1169
  if (nodeType === GCNodeType.DataStore) {
@@ -1067,25 +1173,26 @@ export class GarbageCollector implements IGarbageCollector {
1067
1173
  }
1068
1174
  }
1069
1175
 
1070
- if (!this.configs.shouldRunSweep) {
1071
- return sweepPhaseStats;
1072
- }
1073
-
1074
1176
  // If sweep is enabled, the counts from the mark phase stats do not include nodes that have been
1075
- // deleted in previous runs. Add the deleted node counts to life time stats.
1177
+ // deleted in previous runs. So, add the deleted node counts to life time stats.
1076
1178
  sweepPhaseStats.lifetimeNodeCount += sweepPhaseStats.deletedNodeCount;
1077
1179
  sweepPhaseStats.lifetimeDataStoreCount += sweepPhaseStats.deletedDataStoreCount;
1078
1180
  sweepPhaseStats.lifetimeAttachmentBlobCount += sweepPhaseStats.deletedAttachmentBlobCount;
1079
1181
 
1080
- // The node deleted in current run are counted twice - once in allDeletedNodes and again in
1081
- // markPhaseStats. So, remove them from the life time stats.
1082
- for (const nodeId of currentDeletedNodes) {
1083
- sweepPhaseStats.lifetimeNodeCount--;
1182
+ if (this.configs.shouldRunSweep) {
1183
+ return sweepPhaseStats;
1184
+ }
1185
+
1186
+ // If sweep is not enabled, the current sweep-ready node stats should be added to deleted stats since this
1187
+ // is the final state the node will be in.
1188
+ // If sweep is enabled, this will happen in the run after the GC op round trips back.
1189
+ for (const nodeId of sweepReadyNodes) {
1190
+ sweepPhaseStats.deletedNodeCount++;
1084
1191
  const nodeType = this.runtime.getNodeType(nodeId);
1085
1192
  if (nodeType === GCNodeType.DataStore) {
1086
- sweepPhaseStats.lifetimeDataStoreCount--;
1193
+ sweepPhaseStats.deletedDataStoreCount++;
1087
1194
  } else if (nodeType === GCNodeType.Blob) {
1088
- sweepPhaseStats.lifetimeAttachmentBlobCount--;
1195
+ sweepPhaseStats.deletedAttachmentBlobCount++;
1089
1196
  }
1090
1197
  }
1091
1198
  return sweepPhaseStats;
@@ -3,7 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { MonitoringContext, UsageError } from "@fluidframework/telemetry-utils";
6
+ import {
7
+ MonitoringContext,
8
+ UsageError,
9
+ validatePrecondition,
10
+ } from "@fluidframework/telemetry-utils";
7
11
  import { IContainerRuntimeMetadata } from "../summary";
8
12
  import {
9
13
  nextGCVersion,
@@ -11,9 +15,7 @@ import {
11
15
  defaultSessionExpiryDurationMs,
12
16
  disableTombstoneKey,
13
17
  GCFeatureMatrix,
14
- gcSweepGenerationOptionName,
15
18
  gcTestModeKey,
16
- gcTombstoneGenerationOptionName,
17
19
  GCVersion,
18
20
  gcVersionUpgradeToV4Key,
19
21
  IGarbageCollectorConfigs,
@@ -26,9 +28,11 @@ import {
26
28
  stableGCVersion,
27
29
  throwOnTombstoneLoadOverrideKey,
28
30
  throwOnTombstoneUsageKey,
29
- gcThrowOnTombstoneLoadOptionName,
31
+ gcDisableThrowOnTombstoneLoadOptionName,
32
+ defaultSweepGracePeriodMs,
33
+ gcGenerationOptionName,
30
34
  } from "./gcDefinitions";
31
- import { getGCVersion, shouldAllowGcSweep, shouldAllowGcTombstoneEnforcement } from "./gcHelpers";
35
+ import { getGCVersion, shouldAllowGcSweep } from "./gcHelpers";
32
36
 
33
37
  /**
34
38
  * Generates configurations for the Garbage Collector that it uses to determine what to run and how.
@@ -71,14 +75,6 @@ export function generateGCConfigs(
71
75
  createParams.metadata?.sweepTimeoutMs ?? computeSweepTimeout(sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
72
76
  persistedGcFeatureMatrix = createParams.metadata?.gcFeatureMatrix;
73
77
  } else {
74
- const tombstoneGeneration = createParams.gcOptions[gcTombstoneGenerationOptionName];
75
- const sweepGeneration = createParams.gcOptions[gcSweepGenerationOptionName];
76
-
77
- // Sweep should not be enabled (via sweepGeneration value) without enabling GC mark phase.
78
- if (sweepGeneration !== undefined && createParams.gcOptions.gcAllowed === false) {
79
- throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
80
- }
81
-
82
78
  // This Test Override only applies for new containers
83
79
  const testOverrideSweepTimeoutMs = mc.config.getNumber(
84
80
  "Fluid.GarbageCollection.TestOverride.SweepTimeoutMs",
@@ -95,18 +91,18 @@ export function generateGCConfigs(
95
91
  }
96
92
  sweepTimeoutMs = testOverrideSweepTimeoutMs ?? computeSweepTimeout(sessionExpiryTimeoutMs);
97
93
 
98
- if (tombstoneGeneration !== undefined || sweepGeneration !== undefined) {
99
- persistedGcFeatureMatrix = {
100
- tombstoneGeneration,
101
- sweepGeneration,
102
- };
94
+ const gcGeneration = createParams.gcOptions[gcGenerationOptionName];
95
+ if (gcGeneration !== undefined) {
96
+ persistedGcFeatureMatrix = { gcGeneration };
103
97
  }
104
98
  }
105
99
 
106
- // Is sweepEnabled for this document?
107
- const sweepEnabled = shouldAllowGcSweep(
108
- persistedGcFeatureMatrix ?? {} /* persistedGenerations */,
109
- createParams.gcOptions[gcSweepGenerationOptionName] /* currentGeneration */,
100
+ // The persisted GC generation must indicate Sweep is allowed for this document,
101
+ // according to the GC Generation option provided this session.
102
+ // Note that if no generation option is provided, Sweep is allowed for any document.
103
+ const sweepAllowed = shouldAllowGcSweep(
104
+ persistedGcFeatureMatrix ?? {} /* featureMatrix */,
105
+ createParams.gcOptions[gcGenerationOptionName] /* currentGeneration */,
110
106
  );
111
107
 
112
108
  // If version upgrade is not enabled, fall back to the stable GC version.
@@ -123,27 +119,30 @@ export function generateGCConfigs(
123
119
  * Whether GC should run or not. The following conditions have to be met to run sweep:
124
120
  * 1. GC should be enabled for this container.
125
121
  * 2. GC should not be disabled via disableGC GC option.
126
- * 3. The current GC version should be greater of equal to the GC version in the base snapshot.
127
- * These conditions can be overridden via runGCKey feature flag.
122
+ * 3. The current GC version should be greater or equal to the GC version in the base snapshot.
123
+ *
124
+ * These conditions can be overridden via the RunGC feature flag.
128
125
  */
129
126
  const shouldRunGC =
130
127
  mc.config.getBoolean(runGCKey) ??
131
128
  (gcEnabled && !createParams.gcOptions.disableGC && isGCVersionUpToDate);
132
129
 
133
130
  /**
134
- * Whether sweep should run or not. The following conditions have to be met to run sweep:
131
+ * Whether sweep should run or not. This refers to whether Tombstones should fail on load and whether
132
+ * sweep-ready nodes should be deleted.
135
133
  *
136
- * 1. Overall GC or mark phase must be enabled (this.configs.shouldRunGC).
137
- * 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
138
- * 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
139
- * the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
140
- * 4. Sweep should be enabled for this container. This can be overridden via runSweep
141
- * feature flag.
134
+ * Assuming overall GC is enabled and sweepTimeout is provided, the following conditions have to be met to run sweep:
135
+ *
136
+ * 1. Sweep should be enabled for this container.
137
+ * 2. Sweep should be enabled for this session.
138
+ *
139
+ * These conditions can be overridden via the RunSweep feature flag.
142
140
  */
143
141
  const shouldRunSweep =
144
- shouldRunGC &&
145
- sweepTimeoutMs !== undefined &&
146
- (mc.config.getBoolean(runSweepKey) ?? sweepEnabled);
142
+ !shouldRunGC || sweepTimeoutMs === undefined
143
+ ? false
144
+ : mc.config.getBoolean(runSweepKey) ??
145
+ (sweepAllowed && createParams.gcOptions.enableGCSweep === true);
147
146
 
148
147
  // Override inactive timeout if test config or gc options to override it is set.
149
148
  const inactiveTimeoutMs =
@@ -159,46 +158,45 @@ export function generateGCConfigs(
159
158
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
160
159
  const testMode =
161
160
  mc.config.getBoolean(gcTestModeKey) ?? createParams.gcOptions.runGCInTestMode === true;
162
- // Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
163
- // via feature flags.
164
- const tombstoneMode = !shouldRunSweep && mc.config.getBoolean(disableTombstoneKey) !== true;
161
+ // Whether we are running in tombstone mode. If disabled, tombstone data will not be written to or read from snapshots,
162
+ // and objects will not be marked as tombstoned even if they pass to the "TombstoneReady" state during the session.
163
+ const tombstoneMode = mc.config.getBoolean(disableTombstoneKey) !== true;
165
164
  const runFullGC = createParams.gcOptions.runFullGC;
166
165
 
166
+ const sweepGracePeriodMs =
167
+ createParams.gcOptions.sweepGracePeriodMs ?? defaultSweepGracePeriodMs;
168
+ validatePrecondition(sweepGracePeriodMs >= 0, "sweepGracePeriodMs must be non-negative", {
169
+ sweepGracePeriodMs,
170
+ });
171
+
167
172
  const throwOnInactiveLoad: boolean | undefined = createParams.gcOptions.throwOnInactiveLoad;
168
- const tombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
169
- createParams.metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
170
- createParams.gcOptions[gcTombstoneGenerationOptionName] /* current */,
171
- );
172
173
 
173
174
  const throwOnTombstoneLoadConfig =
174
175
  mc.config.getBoolean(throwOnTombstoneLoadOverrideKey) ??
175
- createParams.gcOptions[gcThrowOnTombstoneLoadOptionName] ??
176
- false;
176
+ createParams.gcOptions[gcDisableThrowOnTombstoneLoadOptionName] !== true;
177
177
  const throwOnTombstoneLoad =
178
- throwOnTombstoneLoadConfig &&
179
- tombstoneEnforcementAllowed &&
180
- !createParams.isSummarizerClient;
178
+ throwOnTombstoneLoadConfig && sweepAllowed && !createParams.isSummarizerClient;
181
179
  const throwOnTombstoneUsage =
182
180
  mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
183
- tombstoneEnforcementAllowed &&
181
+ sweepAllowed &&
184
182
  !createParams.isSummarizerClient;
185
183
 
186
184
  return {
187
- gcEnabled,
188
- sweepEnabled,
189
- shouldRunGC,
190
- shouldRunSweep,
185
+ gcEnabled, // For this document
186
+ sweepEnabled: sweepAllowed, // For this document (based on current GC Generation option)
187
+ shouldRunGC, // For this session
188
+ shouldRunSweep, // For this session
191
189
  runFullGC,
192
190
  testMode,
193
191
  tombstoneMode,
194
192
  sessionExpiryTimeoutMs,
195
193
  sweepTimeoutMs,
194
+ sweepGracePeriodMs,
196
195
  inactiveTimeoutMs,
197
196
  persistedGcFeatureMatrix,
198
197
  gcVersionInBaseSnapshot,
199
198
  gcVersionInEffect,
200
199
  throwOnInactiveLoad,
201
- tombstoneEnforcementAllowed,
202
200
  throwOnTombstoneLoad,
203
201
  throwOnTombstoneUsage,
204
202
  };