@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
@@ -47,6 +47,7 @@ import {
47
47
  wrapError,
48
48
  ITelemetryLoggerExt,
49
49
  UsageError,
50
+ LoggingError,
50
51
  } from "@fluidframework/telemetry-utils";
51
52
  import {
52
53
  DriverHeader,
@@ -87,12 +88,14 @@ import {
87
88
  channelsTreeName,
88
89
  IDataStore,
89
90
  ITelemetryContext,
91
+ } from "@fluidframework/runtime-definitions";
92
+ import type {
90
93
  SerializedIdCompressorWithNoSession,
91
94
  IIdCompressor,
92
95
  IIdCompressorCore,
93
96
  IdCreationRange,
94
97
  SerializedIdCompressorWithOngoingSession,
95
- } from "@fluidframework/runtime-definitions";
98
+ } from "@fluidframework/id-compressor";
96
99
  import {
97
100
  addBlobToSummary,
98
101
  addSummarizeResultToSummary,
@@ -163,7 +166,7 @@ import { formExponentialFn, Throttler } from "./throttler";
163
166
  import {
164
167
  GarbageCollector,
165
168
  GCNodeType,
166
- gcTombstoneGenerationOptionName,
169
+ gcGenerationOptionName,
167
170
  IGarbageCollector,
168
171
  IGCRuntimeOptions,
169
172
  IGCStats,
@@ -194,6 +197,7 @@ import {
194
197
  type LocalContainerRuntimeMessage,
195
198
  type OutboundContainerRuntimeMessage,
196
199
  type UnknownContainerRuntimeMessage,
200
+ ContainerRuntimeGCMessage,
197
201
  } from "./messageTypes";
198
202
 
199
203
  /**
@@ -909,16 +913,18 @@ export class ContainerRuntime
909
913
  metadata?.idCompressorEnabled ?? runtimeOptions.enableRuntimeIdCompressor ?? false;
910
914
  let idCompressor: (IIdCompressor & IIdCompressorCore) | undefined;
911
915
  if (idCompressorEnabled) {
912
- const { IdCompressor, createSessionId } = await import("./id-compressor");
916
+ const { createIdCompressor, deserializeIdCompressor, createSessionId } = await import(
917
+ "@fluidframework/id-compressor"
918
+ );
913
919
 
914
920
  const pendingLocalState = context.pendingLocalState as IPendingRuntimeState;
915
921
 
916
922
  if (pendingLocalState?.pendingIdCompressorState !== undefined) {
917
- idCompressor = IdCompressor.deserialize(pendingLocalState.pendingIdCompressorState);
923
+ idCompressor = deserializeIdCompressor(pendingLocalState.pendingIdCompressorState);
918
924
  } else if (serializedIdCompressor !== undefined) {
919
- idCompressor = IdCompressor.deserialize(serializedIdCompressor, createSessionId());
925
+ idCompressor = deserializeIdCompressor(serializedIdCompressor, createSessionId());
920
926
  } else {
921
- idCompressor = IdCompressor.create(logger);
927
+ idCompressor = createIdCompressor(logger);
922
928
  }
923
929
  }
924
930
 
@@ -1341,8 +1347,7 @@ export class ContainerRuntime
1341
1347
  eventName: "GCFeatureMatrix",
1342
1348
  metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
1343
1349
  inputs: JSON.stringify({
1344
- gcOptions_gcTombstoneGeneration:
1345
- this.runtimeOptions.gcOptions[gcTombstoneGenerationOptionName],
1350
+ gcOptions_gcGeneration: this.runtimeOptions.gcOptions[gcGenerationOptionName],
1346
1351
  }),
1347
1352
  });
1348
1353
 
@@ -1447,6 +1452,7 @@ export class ContainerRuntime
1447
1452
  // GC runs in summarizer client and needs access to the real (non-proxy) active information. The proxy
1448
1453
  // delta manager would always return false for summarizer client.
1449
1454
  activeConnection: () => this.innerDeltaManager.active,
1455
+ submitMessage: (message: ContainerRuntimeGCMessage) => this.submit(message),
1450
1456
  });
1451
1457
 
1452
1458
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
@@ -2082,6 +2088,9 @@ export class ContainerRuntime
2082
2088
  throw new Error("chunkedOp not expected here");
2083
2089
  case ContainerMessageType.Rejoin:
2084
2090
  throw new Error("rejoin not expected here");
2091
+ case ContainerMessageType.GC:
2092
+ // GC op is only sent in summarizer which should never have stashed ops.
2093
+ throw new LoggingError("GC op not expected to be stashed in summarizer");
2085
2094
  default: {
2086
2095
  // This should be extremely rare for stashed ops.
2087
2096
  // It would require a newer runtime stashing ops and then an older one applying them,
@@ -2335,6 +2344,9 @@ export class ContainerRuntime
2335
2344
  this.idCompressor.finalizeCreationRange(messageWithContext.message.contents);
2336
2345
  }
2337
2346
  break;
2347
+ case ContainerMessageType.GC:
2348
+ this.garbageCollector.processMessage(messageWithContext.message, local);
2349
+ break;
2338
2350
  case ContainerMessageType.ChunkedOp:
2339
2351
  case ContainerMessageType.Rejoin:
2340
2352
  break;
@@ -2632,18 +2644,28 @@ export class ContainerRuntime
2632
2644
  }
2633
2645
 
2634
2646
  private isContainerMessageDirtyable({ type, contents }: OutboundContainerRuntimeMessage) {
2635
- // For legacy purposes, exclude the old built-in AgentScheduler from dirty consideration as a special-case.
2636
- // Ultimately we should have no special-cases from the ContainerRuntime's perspective.
2637
- if (type === ContainerMessageType.Attach) {
2638
- const attachMessage = contents as InboundAttachMessage;
2639
- if (attachMessage.id === agentSchedulerId) {
2640
- return false;
2647
+ // Certain container runtime messages should not mark the container dirty such as the old built-in
2648
+ // AgentScheduler and Garbage collector messages.
2649
+ switch (type) {
2650
+ case ContainerMessageType.Attach: {
2651
+ const attachMessage = contents as InboundAttachMessage;
2652
+ if (attachMessage.id === agentSchedulerId) {
2653
+ return false;
2654
+ }
2655
+ break;
2656
+ }
2657
+ case ContainerMessageType.FluidDataStoreOp: {
2658
+ const envelope = contents;
2659
+ if (envelope.address === agentSchedulerId) {
2660
+ return false;
2661
+ }
2662
+ break;
2641
2663
  }
2642
- } else if (type === ContainerMessageType.FluidDataStoreOp) {
2643
- const envelope = contents;
2644
- if (envelope.address === agentSchedulerId) {
2664
+ case ContainerMessageType.GC: {
2645
2665
  return false;
2646
2666
  }
2667
+ default:
2668
+ break;
2647
2669
  }
2648
2670
  return true;
2649
2671
  }
@@ -2874,7 +2896,7 @@ export class ContainerRuntime
2874
2896
  * @param usedRoutes - The routes that are used in all nodes in this Container.
2875
2897
  * @see IGarbageCollectionRuntime.updateUsedRoutes
2876
2898
  */
2877
- public updateUsedRoutes(usedRoutes: string[]) {
2899
+ public updateUsedRoutes(usedRoutes: readonly string[]) {
2878
2900
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
2879
2901
  // summarizing is required and asserted by the the summarizer node. We are the root and are
2880
2902
  // always referenced, so the used routes is only self-route (empty string).
@@ -2888,7 +2910,7 @@ export class ContainerRuntime
2888
2910
  * This is called to update objects whose routes are unused.
2889
2911
  * @param unusedRoutes - Data store and attachment blob routes that are unused in this Container.
2890
2912
  */
2891
- public updateUnusedRoutes(unusedRoutes: string[]) {
2913
+ public updateUnusedRoutes(unusedRoutes: readonly string[]) {
2892
2914
  const { blobManagerRoutes, dataStoreRoutes } =
2893
2915
  this.getDataStoreAndBlobManagerRoutes(unusedRoutes);
2894
2916
  this.blobManager.updateUnusedRoutes(blobManagerRoutes);
@@ -2898,7 +2920,7 @@ export class ContainerRuntime
2898
2920
  /**
2899
2921
  * @deprecated Replaced by deleteSweepReadyNodes.
2900
2922
  */
2901
- public deleteUnusedNodes(unusedRoutes: string[]): string[] {
2923
+ public deleteUnusedNodes(unusedRoutes: readonly string[]): string[] {
2902
2924
  throw new Error("deleteUnusedRoutes should not be called");
2903
2925
  }
2904
2926
 
@@ -2907,7 +2929,7 @@ export class ContainerRuntime
2907
2929
  * @param sweepReadyRoutes - The routes of nodes that are sweep ready and should be deleted.
2908
2930
  * @returns The routes of nodes that were deleted.
2909
2931
  */
2910
- public deleteSweepReadyNodes(sweepReadyRoutes: string[]): string[] {
2932
+ public deleteSweepReadyNodes(sweepReadyRoutes: readonly string[]): readonly string[] {
2911
2933
  const { dataStoreRoutes, blobManagerRoutes } =
2912
2934
  this.getDataStoreAndBlobManagerRoutes(sweepReadyRoutes);
2913
2935
 
@@ -2919,7 +2941,7 @@ export class ContainerRuntime
2919
2941
  * This is called to update objects that are tombstones.
2920
2942
  * @param tombstonedRoutes - Data store and attachment blob routes that are tombstones in this Container.
2921
2943
  */
2922
- public updateTombstonedRoutes(tombstonedRoutes: string[]) {
2944
+ public updateTombstonedRoutes(tombstonedRoutes: readonly string[]) {
2923
2945
  const { blobManagerRoutes, dataStoreRoutes } =
2924
2946
  this.getDataStoreAndBlobManagerRoutes(tombstonedRoutes);
2925
2947
  this.blobManager.updateTombstonedRoutes(blobManagerRoutes);
@@ -2979,7 +3001,7 @@ export class ContainerRuntime
2979
3001
  * @returns Two route lists - One that contains routes for blob manager and another one that contains routes
2980
3002
  * for data stores.
2981
3003
  */
2982
- private getDataStoreAndBlobManagerRoutes(routes: string[]) {
3004
+ private getDataStoreAndBlobManagerRoutes(routes: readonly string[]) {
2983
3005
  const blobManagerRoutes: string[] = [];
2984
3006
  const dataStoreRoutes: string[] = [];
2985
3007
  for (const route of routes) {
@@ -3056,10 +3078,10 @@ export class ContainerRuntime
3056
3078
  );
3057
3079
  }
3058
3080
 
3059
- // If there are pending (unacked ops), the summary will not be eventual consistent and it may even be
3060
- // incorrect. So, wait for the container to be saved with a timeout. If the container is not saved
3061
- // within the timeout, check if it should be failed or can continue.
3062
- if (this.validateSummaryBeforeUpload && this.hasPendingMessages()) {
3081
+ // If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
3082
+ // and it may even be incorrect. So, wait for the container to be saved with a timeout. If the container is not
3083
+ // saved within the timeout, check if it should be failed or can continue.
3084
+ if (this.validateSummaryBeforeUpload && this.isDirty) {
3063
3085
  const countBefore = this.pendingMessagesCount;
3064
3086
  // The timeout for waiting for pending ops can be overridden via configurations.
3065
3087
  const pendingOpsTimeout =
@@ -3081,7 +3103,7 @@ export class ContainerRuntime
3081
3103
  // happens, whether we attempted to wait for these ops to be acked and what was the result.
3082
3104
  summaryNumberLogger.sendTelemetryEvent({
3083
3105
  eventName: "PendingOpsWhileSummarizing",
3084
- saved: this.hasPendingMessages() ? false : true,
3106
+ saved: !this.isDirty,
3085
3107
  timeout: pendingOpsTimeout,
3086
3108
  countBefore,
3087
3109
  countAfter: this.pendingMessagesCount,
@@ -3360,7 +3382,7 @@ export class ContainerRuntime
3360
3382
  }
3361
3383
 
3362
3384
  /**
3363
- * This helper is called during summarization. If there are pending ops, it will return a failed summarize result
3385
+ * This helper is called during summarization. If the container is dirty, it will return a failed summarize result
3364
3386
  * (IBaseSummarizeResult) unless this is the final summarize attempt and SkipFailingIncorrectSummary option is set.
3365
3387
  * @param logger - The logger to be used for sending telemetry.
3366
3388
  * @param referenceSequenceNumber - The reference sequence number of the summary attempt.
@@ -3376,7 +3398,7 @@ export class ContainerRuntime
3376
3398
  finalAttempt: boolean,
3377
3399
  beforeSummaryGeneration: boolean,
3378
3400
  ): Promise<IBaseSummarizeResult | undefined> {
3379
- if (!this.hasPendingMessages()) {
3401
+ if (!this.isDirty) {
3380
3402
  return;
3381
3403
  }
3382
3404
 
@@ -3724,6 +3746,7 @@ export class ContainerRuntime
3724
3746
  /**
3725
3747
  * Finds the right store and asks it to resubmit the message. This typically happens when we
3726
3748
  * reconnect and there are pending messages.
3749
+ * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
3727
3750
  * @param message - The original LocalContainerRuntimeMessage.
3728
3751
  * @param localOpMetadata - The local metadata associated with the original message.
3729
3752
  */
@@ -3752,6 +3775,9 @@ export class ContainerRuntime
3752
3775
  case ContainerMessageType.Rejoin:
3753
3776
  this.submit(message);
3754
3777
  break;
3778
+ case ContainerMessageType.GC:
3779
+ // GC op is only sent in summarizer which should never reconnect.
3780
+ throw new LoggingError("GC op not expected to be resubmitted in summarizer");
3755
3781
  default: {
3756
3782
  // This case should be very rare - it would imply an op was stashed from a
3757
3783
  // future version of runtime code and now is being applied on an older version
package/src/dataStores.ts CHANGED
@@ -61,13 +61,8 @@ import {
61
61
  } from "./dataStoreContext";
62
62
  import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs";
63
63
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
64
- import { GCNodeType, disableDatastoreSweepKey, sendGCUnexpectedUsageEvent } from "./gc";
65
- import {
66
- summarizerClientType,
67
- IContainerRuntimeMetadata,
68
- nonDataStorePaths,
69
- rootHasIsolatedChannels,
70
- } from "./summary";
64
+ import { GCNodeType, disableDatastoreSweepKey } from "./gc";
65
+ import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summary";
71
66
 
72
67
  type PendingAliasResolve = (success: boolean) => void;
73
68
 
@@ -281,6 +276,19 @@ export class DataStores implements IDisposable {
281
276
  }
282
277
 
283
278
  const context = this.contexts.get(aliasMessage.internalId);
279
+ // If the data store has been deleted, log an error and ignore this message. This helps prevent document
280
+ // corruption in case a deleted data store accidentally submitted a signal.
281
+ if (
282
+ this.checkAndLogIfDeleted(
283
+ aliasMessage.internalId,
284
+ context,
285
+ "Changed",
286
+ "processAliasMessageCore",
287
+ )
288
+ ) {
289
+ return false;
290
+ }
291
+
284
292
  if (context === undefined) {
285
293
  this.mc.logger.sendErrorEvent({
286
294
  eventName: "AliasFluidDataStoreNotFound",
@@ -382,18 +390,43 @@ export class DataStores implements IDisposable {
382
390
 
383
391
  public resubmitDataStoreOp(envelope: IEnvelope, localOpMetadata: unknown) {
384
392
  const context = this.contexts.get(envelope.address);
393
+ // If the data store has been deleted, log an error and throw an error. If there are local changes for a
394
+ // deleted data store, it can otherwise lead to inconsistent state when compared to other clients.
395
+ if (
396
+ this.checkAndLogIfDeleted(envelope.address, context, "Changed", "resubmitDataStoreOp")
397
+ ) {
398
+ throw new DataCorruptionError("Context is deleted!", {
399
+ callSite: "resubmitDataStoreOp",
400
+ ...tagCodeArtifacts({ id: envelope.address }),
401
+ });
402
+ }
385
403
  assert(!!context, 0x160 /* "There should be a store context for the op" */);
386
404
  context.reSubmit(envelope.contents, localOpMetadata);
387
405
  }
388
406
 
389
407
  public rollbackDataStoreOp(envelope: IEnvelope, localOpMetadata: unknown) {
390
408
  const context = this.contexts.get(envelope.address);
409
+ // If the data store has been deleted, log an error and throw an error. If there are local changes for a
410
+ // deleted data store, it can otherwise lead to inconsistent state when compared to other clients.
411
+ if (
412
+ this.checkAndLogIfDeleted(envelope.address, context, "Changed", "rollbackDataStoreOp")
413
+ ) {
414
+ throw new DataCorruptionError("Context is deleted!", {
415
+ callSite: "rollbackDataStoreOp",
416
+ ...tagCodeArtifacts({ id: envelope.address }),
417
+ });
418
+ }
391
419
  assert(!!context, 0x2e8 /* "There should be a store context for the op" */);
392
420
  context.rollback(envelope.contents, localOpMetadata);
393
421
  }
394
422
 
395
423
  public async applyStashedOp(envelope: IEnvelope): Promise<unknown> {
396
424
  const context = this.contexts.get(envelope.address);
425
+ // If the data store has been deleted, log an error and ignore this message. This helps prevent document
426
+ // corruption in case the data store that stashed the op is deleted.
427
+ if (this.checkAndLogIfDeleted(envelope.address, context, "Changed", "applyStashedOp")) {
428
+ return undefined;
429
+ }
397
430
  assert(!!context, 0x161 /* "There should be a store context for the op" */);
398
431
  return context.applyStashedOp(envelope.contents);
399
432
  }
@@ -411,8 +444,21 @@ export class DataStores implements IDisposable {
411
444
  ) {
412
445
  const envelope = message.contents as IEnvelope;
413
446
  const transformed = { ...message, contents: envelope.contents };
414
- this.validateNotDeleted(envelope.address);
415
447
  const context = this.contexts.get(envelope.address);
448
+
449
+ // If the data store has been deleted, log an error and ignore this message. This helps prevent document
450
+ // corruption in case a deleted data store accidentally submitted an op.
451
+ if (
452
+ this.checkAndLogIfDeleted(
453
+ envelope.address,
454
+ context,
455
+ "Changed",
456
+ "processFluidDataStoreOp",
457
+ )
458
+ ) {
459
+ return;
460
+ }
461
+
416
462
  assert(!!context, 0x162 /* "There should be a store context for the op" */);
417
463
  context.process(transformed, local, localMessageMetadata);
418
464
 
@@ -430,7 +476,22 @@ export class DataStores implements IDisposable {
430
476
  requestHeaderData: RuntimeHeaderData,
431
477
  ): Promise<FluidDataStoreContext> {
432
478
  const headerData = { ...defaultRuntimeHeaderData, ...requestHeaderData };
433
- this.validateNotDeleted(id, headerData);
479
+ if (
480
+ this.checkAndLogIfDeleted(
481
+ id,
482
+ this.contexts.get(id),
483
+ "Requested",
484
+ "getDataStore",
485
+ requestHeaderData,
486
+ )
487
+ ) {
488
+ // The requested data store has been deleted by gc. Create a 404 response exception.
489
+ const request: IRequest = { url: id };
490
+ throw responseToException(
491
+ createResponseError(404, "DataStore was deleted", request),
492
+ request,
493
+ );
494
+ }
434
495
 
435
496
  const context = await this.contexts.getBoundOrRemoted(id, headerData.wait);
436
497
  if (context === undefined) {
@@ -448,8 +509,16 @@ export class DataStores implements IDisposable {
448
509
  id: string,
449
510
  requestHeaderData: RuntimeHeaderData,
450
511
  ): Promise<FluidDataStoreContext | undefined> {
451
- // If the data store has been deleted, return undefined.
452
- if (this.checkIfDeleted(id, requestHeaderData)) {
512
+ // If the data store has been deleted, log an error and return undefined.
513
+ if (
514
+ this.checkAndLogIfDeleted(
515
+ id,
516
+ this.contexts.get(id),
517
+ "Requested",
518
+ "getDataStoreIfAvailable",
519
+ requestHeaderData,
520
+ )
521
+ ) {
453
522
  return undefined;
454
523
  }
455
524
  const headerData = { ...defaultRuntimeHeaderData, ...requestHeaderData };
@@ -461,55 +530,43 @@ export class DataStores implements IDisposable {
461
530
  }
462
531
 
463
532
  /**
464
- * Checks if the data store has been deleted by GC.
465
- * @param id - data store id
466
- * @param request - the request information to log if the validation detects the data store has been deleted
467
- * @param requestHeaderData - the request header information to log if the validation detects the data store has been deleted
533
+ * Checks if the data store has been deleted by GC. If so, log an error.
534
+ * @param id - The data store's id.
535
+ * @param context - The data store context.
536
+ * @param callSite - The function name this is called from.
537
+ * @param requestHeaderData - The request header information to log if the data store is deleted.
468
538
  * @returns true if the data store is deleted. Otherwise, returns false.
469
539
  */
470
- private checkIfDeleted(id: string, requestHeaderData?: RuntimeHeaderData) {
540
+ private checkAndLogIfDeleted(
541
+ id: string,
542
+ context: FluidDataStoreContext | undefined,
543
+ deletedLogSuffix: string,
544
+ callSite: string,
545
+ requestHeaderData?: RuntimeHeaderData,
546
+ ) {
471
547
  const dataStoreNodePath = `/${id}`;
472
548
  if (!this.isDataStoreDeleted(dataStoreNodePath)) {
473
549
  return false;
474
550
  }
475
- assert(
476
- !this.contexts.has(id),
477
- 0x570 /* Inconsistent state! GC says the data store is deleted, but the data store is not deleted from the runtime. */,
478
- );
479
- sendGCUnexpectedUsageEvent(
480
- this.mc,
481
- {
482
- eventName: "GC_Deleted_DataStore_Requested",
483
- category: "error",
484
- isSummarizerClient: this.runtime.clientDetails.type === summarizerClientType,
485
- id,
486
- headers: JSON.stringify(requestHeaderData),
487
- gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
488
- },
489
- undefined /* packagePath */,
490
- );
491
- return true;
492
- }
493
551
 
494
- /**
495
- * Validate that the data store had not been deleted by GC.
496
- * @param id - data store id
497
- * @param requestHeaderData - the request header information to log if the validation detects the data store has been deleted
498
- */
499
- private validateNotDeleted(id: string, requestHeaderData?: RuntimeHeaderData) {
500
- if (this.checkIfDeleted(id, requestHeaderData)) {
501
- // The requested data store is removed by gc. Create a 404 gc response exception.
502
- const request: IRequest = { url: id };
503
- throw responseToException(
504
- createResponseError(404, "DataStore was deleted", request),
505
- request,
506
- );
507
- }
552
+ this.mc.logger.sendErrorEvent({
553
+ eventName: `GC_Deleted_DataStore_${deletedLogSuffix}`,
554
+ ...tagCodeArtifacts({ id }),
555
+ callSite,
556
+ headers: JSON.stringify(requestHeaderData),
557
+ exists: context !== undefined,
558
+ });
559
+ return true;
508
560
  }
509
561
 
510
562
  public processSignal(fluidDataStoreId: string, message: IInboundSignalMessage, local: boolean) {
511
- this.validateNotDeleted(fluidDataStoreId);
512
563
  const context = this.contexts.get(fluidDataStoreId);
564
+ // If the data store has been deleted, log an error and ignore this message. This helps prevent document
565
+ // corruption in case a deleted data store accidentally submitted a signal.
566
+ if (this.checkAndLogIfDeleted(fluidDataStoreId, context, "Changed", "processSignal")) {
567
+ return;
568
+ }
569
+
513
570
  if (!context) {
514
571
  // Attach message may not have been processed yet
515
572
  assert(!local, 0x163 /* "Missing datastore for local signal" */);
@@ -711,7 +768,7 @@ export class DataStores implements IDisposable {
711
768
  * After GC has run, called to notify this Container's data stores of routes that are used in it.
712
769
  * @param usedRoutes - The routes that are used in all data stores in this Container.
713
770
  */
714
- public updateUsedRoutes(usedRoutes: string[]) {
771
+ public updateUsedRoutes(usedRoutes: readonly string[]) {
715
772
  // Get a map of data store ids to routes used in it.
716
773
  const usedDataStoreRoutes = unpackChildNodesUsedRoutes(usedRoutes);
717
774
 
@@ -733,7 +790,7 @@ export class DataStores implements IDisposable {
733
790
  * This is called to update objects whose routes are unused. The unused objects are deleted.
734
791
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
735
792
  */
736
- public updateUnusedRoutes(unusedRoutes: string[]) {
793
+ public updateUnusedRoutes(unusedRoutes: readonly string[]) {
737
794
  for (const route of unusedRoutes) {
738
795
  const pathParts = route.split("/");
739
796
  // Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
@@ -756,7 +813,7 @@ export class DataStores implements IDisposable {
756
813
  * be deleted.
757
814
  * @returns The routes of data stores and its objects that were deleted.
758
815
  */
759
- public deleteSweepReadyNodes(sweepReadyDataStoreRoutes: string[]): string[] {
816
+ public deleteSweepReadyNodes(sweepReadyDataStoreRoutes: readonly string[]): readonly string[] {
760
817
  // If sweep for data stores is not enabled, return empty list indicating nothing is deleted.
761
818
  if (this.mc.config.getBoolean(disableDatastoreSweepKey) === true) {
762
819
  return [];
@@ -765,23 +822,25 @@ export class DataStores implements IDisposable {
765
822
  const pathParts = route.split("/");
766
823
  const dataStoreId = pathParts[1];
767
824
 
768
- // TODO: GC:Validation - Skip any routes already deleted
769
825
  // Ignore sub-data store routes because a data store and its sub-routes are deleted together, so, we only
770
826
  // need to delete the data store.
771
827
  if (pathParts.length > 2) {
772
828
  continue;
773
829
  }
774
830
 
775
- if (!this.contexts.has(dataStoreId)) {
831
+ const dataStoreContext = this.contexts.get(dataStoreId);
832
+ if (dataStoreContext === undefined) {
776
833
  this.mc.logger.sendErrorEvent({
777
834
  eventName: "DeletedDataStoreNotFound",
778
- dataStoreId,
835
+ ...tagCodeArtifacts({ id: dataStoreId }),
836
+ details: {
837
+ alreadyDeleted: this.isDataStoreDeleted(dataStoreId),
838
+ },
779
839
  });
840
+ continue;
780
841
  }
781
842
 
782
- const dataStore = this.contexts.get(dataStoreId);
783
- assert(dataStore !== undefined, 0x571 /* Attempting to delete unknown dataStore */);
784
- dataStore.delete();
843
+ dataStoreContext.delete();
785
844
 
786
845
  // Delete the contexts of sweep ready data stores.
787
846
  this.contexts.delete(dataStoreId);
@@ -796,7 +855,7 @@ export class DataStores implements IDisposable {
796
855
  * scenarios with accessing deleted content without actually deleting content from summaries.
797
856
  * @param tombstonedRoutes - The routes that are tombstones in all data stores in this Container.
798
857
  */
799
- public updateTombstonedRoutes(tombstonedRoutes: string[]) {
858
+ public updateTombstonedRoutes(tombstonedRoutes: readonly string[]) {
800
859
  const tombstonedDataStoresSet: Set<string> = new Set();
801
860
  for (const route of tombstonedRoutes) {
802
861
  const pathParts = route.split("/");
@@ -50,16 +50,19 @@ Mark phase is enabled by default for a container. It is enabled during creation
50
50
 
51
51
  ### Sweep phase
52
52
 
53
- In this phase, the GC algorithm identifies all Fluid objects that have been unreferenced for a specific amount of time (typically 30-40 days) and deletes them.
54
- Objects are only swept once the GC system is sure that they could never be referenced again by any active clients, i.e., clients that have the object in memory and could reference it.
55
- The Fluid Runtime enforces a maximum session length (configurable) in order to guarantee an object is safe to delete after sufficient time has elapsed.
53
+ In this phase, the GC algorithm deletes any Fluid object that has been unreferenced for a sufficient time to guarantee
54
+ they could never be referenced again by any active clients, i.e., clients that have the object in memory and could reference it again.
55
+ The Fluid Runtime enforces a maximum session length (configurable) in order to guarantee all in-memory objects are cleared before
56
+ it concludes an object is safe to delete.
56
57
 
57
- GC sweep phase has not been enabled by default yet. A "soft" version of Sweep called "Tombstone Mode" is enabled by default
58
- as part of the Mark Phase when Sweep is disabled. In this mode, any object that GC determines is ready to be deleted is
59
- marked as a "Tombstone", which triggers certain logging events and/or behavior changes if/when that Tombstoned object is
60
- accessed by the application.
58
+ GC sweep phase runs in two stages:
61
59
 
62
- Tombstone is intended for use by early adopters of GC and is documented in more detail [here](./gcEarlyAdoption.md).
60
+ - The first stage is the "Tombstone" stage, where objects are marked as Tombstones, meaning GC believes they will
61
+ never be referenced again and are safe to delete. They are not yet deleted at this point, but any attempt to
62
+ load them will fail. This way, there's a chance to recover a Tombstoned object in case we detect it's still being used.
63
+ - The second stage is the "Sweep" or "Delete" stage, where the objects are fully deleted.
64
+ This occurs after a configurable delay called the "Sweep Grace Period", to give time for application teams
65
+ to monitor for Tombstone-related errors and react before delete occurs.
63
66
 
64
67
  ## GC Configuration
65
68
 
@@ -67,7 +70,8 @@ The default configuration for GC today is:
67
70
 
68
71
  - GC Mark Phase is **enabled**, including Tombstone Mode
69
72
  - Session Expiry is **enabled**
70
- - GC Sweep Phase is **disabled**
73
+ - The "Tombstone" stage of Sweep Phase is **enabled** (attempting to load a tombstoned object will fail)
74
+ - The "Delete" stage of Sweep Phase is **disabled**
71
75
  - Note: Once enabled, Sweep will only run for documents created from that point forward
72
76
 
73
77
  ### Techniques used for configuration
@@ -94,12 +98,7 @@ covered in the [Advanced Configuration](./gcEarlyAdoption.md#more-advanced-confi
94
98
 
95
99
  ### Enabling Sweep Phase
96
100
 
97
- To enable Sweep Phase for new documents, you must set the `gcSweepGeneration` GC Option to a number, e.g. 0 to start.
98
- The full semantics of this GC Option are discussed [here](./gcEarlyAdoption.md#more-about-gcsweepgeneration-and-gctombstonegeneration).
99
- Note that this will disabled Tombstone Mode.
100
-
101
- A full treatment of Tombstone and Sweep configuration can be found in
102
- [this companion document geared towards early adopters of GC](./gcEarlyAdoption.md).
101
+ To enable the Sweep Phase for new documents, you must set the `enableGCSweep` GC Option to true.
103
102
 
104
103
  ### More Advanced Configuration
105
104