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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +20 -5
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +57 -15
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +88 -51
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +205 -300
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +51 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +40 -32
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +227 -161
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/garbageCollectionConstants.d.ts +19 -0
  28. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  29. package/dist/garbageCollectionConstants.js +34 -0
  30. package/dist/garbageCollectionConstants.js.map +1 -0
  31. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  32. package/dist/gcSweepReadyUsageDetection.js +5 -14
  33. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/opLifecycle/batchManager.d.ts +30 -0
  39. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  40. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  41. package/dist/opLifecycle/batchManager.js.map +1 -0
  42. package/dist/opLifecycle/definitions.d.ts +40 -0
  43. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  44. package/dist/opLifecycle/definitions.js +7 -0
  45. package/dist/opLifecycle/definitions.js.map +1 -0
  46. package/dist/opLifecycle/index.d.ts +12 -0
  47. package/dist/opLifecycle/index.d.ts.map +1 -0
  48. package/dist/opLifecycle/index.js +21 -0
  49. package/dist/opLifecycle/index.js.map +1 -0
  50. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  52. package/dist/opLifecycle/opCompressor.js +53 -0
  53. package/dist/opLifecycle/opCompressor.js.map +1 -0
  54. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  55. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  56. package/dist/opLifecycle/opDecompressor.js +72 -0
  57. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  58. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  60. package/dist/opLifecycle/opSplitter.js +61 -0
  61. package/dist/opLifecycle/opSplitter.js.map +1 -0
  62. package/dist/opLifecycle/outbox.d.ts +47 -0
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  64. package/dist/opLifecycle/outbox.js +153 -0
  65. package/dist/opLifecycle/outbox.js.map +1 -0
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  68. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  69. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/pendingStateManager.d.ts +6 -26
  74. package/dist/pendingStateManager.d.ts.map +1 -1
  75. package/dist/pendingStateManager.js +42 -62
  76. package/dist/pendingStateManager.js.map +1 -1
  77. package/dist/runningSummarizer.d.ts +3 -2
  78. package/dist/runningSummarizer.d.ts.map +1 -1
  79. package/dist/runningSummarizer.js +10 -3
  80. package/dist/runningSummarizer.js.map +1 -1
  81. package/dist/scheduleManager.js.map +1 -1
  82. package/dist/summarizer.js +7 -2
  83. package/dist/summarizer.js.map +1 -1
  84. package/dist/summarizerClientElection.js +1 -1
  85. package/dist/summarizerClientElection.js.map +1 -1
  86. package/dist/summarizerHeuristics.d.ts.map +1 -1
  87. package/dist/summarizerHeuristics.js +0 -3
  88. package/dist/summarizerHeuristics.js.map +1 -1
  89. package/dist/summarizerTypes.d.ts +19 -2
  90. package/dist/summarizerTypes.d.ts.map +1 -1
  91. package/dist/summarizerTypes.js.map +1 -1
  92. package/dist/summaryFormat.d.ts +4 -2
  93. package/dist/summaryFormat.d.ts.map +1 -1
  94. package/dist/summaryFormat.js +2 -2
  95. package/dist/summaryFormat.js.map +1 -1
  96. package/dist/summaryGenerator.d.ts.map +1 -1
  97. package/dist/summaryGenerator.js +3 -2
  98. package/dist/summaryGenerator.js.map +1 -1
  99. package/dist/summaryManager.d.ts.map +1 -1
  100. package/dist/summaryManager.js +10 -6
  101. package/dist/summaryManager.js.map +1 -1
  102. package/garbageCollection.md +27 -22
  103. package/lib/blobManager.d.ts +20 -5
  104. package/lib/blobManager.d.ts.map +1 -1
  105. package/lib/blobManager.js +59 -17
  106. package/lib/blobManager.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +88 -51
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +203 -297
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts.map +1 -1
  112. package/lib/dataStore.js +6 -0
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts +14 -21
  115. package/lib/dataStoreContext.d.ts.map +1 -1
  116. package/lib/dataStoreContext.js +75 -61
  117. package/lib/dataStoreContext.js.map +1 -1
  118. package/lib/dataStoreContexts.js +1 -1
  119. package/lib/dataStoreContexts.js.map +1 -1
  120. package/lib/dataStores.d.ts +11 -10
  121. package/lib/dataStores.d.ts.map +1 -1
  122. package/lib/dataStores.js +53 -22
  123. package/lib/dataStores.js.map +1 -1
  124. package/lib/garbageCollection.d.ts +40 -32
  125. package/lib/garbageCollection.d.ts.map +1 -1
  126. package/lib/garbageCollection.js +220 -154
  127. package/lib/garbageCollection.js.map +1 -1
  128. package/lib/garbageCollectionConstants.d.ts +19 -0
  129. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  130. package/lib/garbageCollectionConstants.js +31 -0
  131. package/lib/garbageCollectionConstants.js.map +1 -0
  132. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  133. package/lib/gcSweepReadyUsageDetection.js +4 -13
  134. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  135. package/lib/index.d.ts +6 -6
  136. package/lib/index.d.ts.map +1 -1
  137. package/lib/index.js +3 -4
  138. package/lib/index.js.map +1 -1
  139. package/lib/opLifecycle/batchManager.d.ts +30 -0
  140. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  141. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  142. package/lib/opLifecycle/batchManager.js.map +1 -0
  143. package/lib/opLifecycle/definitions.d.ts +40 -0
  144. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  145. package/lib/opLifecycle/definitions.js +6 -0
  146. package/lib/opLifecycle/definitions.js.map +1 -0
  147. package/lib/opLifecycle/index.d.ts +12 -0
  148. package/lib/opLifecycle/index.d.ts.map +1 -0
  149. package/lib/opLifecycle/index.js +11 -0
  150. package/lib/opLifecycle/index.js.map +1 -0
  151. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  152. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  153. package/lib/opLifecycle/opCompressor.js +49 -0
  154. package/lib/opLifecycle/opCompressor.js.map +1 -0
  155. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  156. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  157. package/lib/opLifecycle/opDecompressor.js +68 -0
  158. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  159. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  160. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  161. package/lib/opLifecycle/opSplitter.js +57 -0
  162. package/lib/opLifecycle/opSplitter.js.map +1 -0
  163. package/lib/opLifecycle/outbox.d.ts +47 -0
  164. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  165. package/lib/opLifecycle/outbox.js +149 -0
  166. package/lib/opLifecycle/outbox.js.map +1 -0
  167. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  168. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  169. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  170. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  171. package/lib/packageVersion.d.ts +1 -1
  172. package/lib/packageVersion.js +1 -1
  173. package/lib/packageVersion.js.map +1 -1
  174. package/lib/pendingStateManager.d.ts +6 -26
  175. package/lib/pendingStateManager.d.ts.map +1 -1
  176. package/lib/pendingStateManager.js +42 -62
  177. package/lib/pendingStateManager.js.map +1 -1
  178. package/lib/runningSummarizer.d.ts +3 -2
  179. package/lib/runningSummarizer.d.ts.map +1 -1
  180. package/lib/runningSummarizer.js +10 -3
  181. package/lib/runningSummarizer.js.map +1 -1
  182. package/lib/scheduleManager.js.map +1 -1
  183. package/lib/summarizer.js +7 -2
  184. package/lib/summarizer.js.map +1 -1
  185. package/lib/summarizerClientElection.js +1 -1
  186. package/lib/summarizerClientElection.js.map +1 -1
  187. package/lib/summarizerHeuristics.d.ts.map +1 -1
  188. package/lib/summarizerHeuristics.js +0 -3
  189. package/lib/summarizerHeuristics.js.map +1 -1
  190. package/lib/summarizerTypes.d.ts +19 -2
  191. package/lib/summarizerTypes.d.ts.map +1 -1
  192. package/lib/summarizerTypes.js.map +1 -1
  193. package/lib/summaryFormat.d.ts +4 -2
  194. package/lib/summaryFormat.d.ts.map +1 -1
  195. package/lib/summaryFormat.js +1 -1
  196. package/lib/summaryFormat.js.map +1 -1
  197. package/lib/summaryGenerator.d.ts.map +1 -1
  198. package/lib/summaryGenerator.js +3 -2
  199. package/lib/summaryGenerator.js.map +1 -1
  200. package/lib/summaryManager.d.ts.map +1 -1
  201. package/lib/summaryManager.js +10 -6
  202. package/lib/summaryManager.js.map +1 -1
  203. package/package.json +32 -71
  204. package/prettier.config.cjs +8 -0
  205. package/src/blobManager.ts +74 -19
  206. package/src/containerRuntime.ts +286 -369
  207. package/src/dataStore.ts +13 -1
  208. package/src/dataStoreContext.ts +100 -76
  209. package/src/dataStoreContexts.ts +1 -1
  210. package/src/dataStores.ts +61 -22
  211. package/src/garbageCollection.ts +282 -163
  212. package/src/garbageCollectionConstants.ts +35 -0
  213. package/src/gcSweepReadyUsageDetection.ts +3 -11
  214. package/src/index.ts +9 -8
  215. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +42 -28
  216. package/src/opLifecycle/definitions.ts +44 -0
  217. package/src/opLifecycle/index.ts +17 -0
  218. package/src/opLifecycle/opCompressor.ts +64 -0
  219. package/src/opLifecycle/opDecompressor.ts +84 -0
  220. package/src/opLifecycle/opSplitter.ts +78 -0
  221. package/src/opLifecycle/outbox.ts +204 -0
  222. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  223. package/src/packageVersion.ts +1 -1
  224. package/src/pendingStateManager.ts +57 -96
  225. package/src/runningSummarizer.ts +11 -3
  226. package/src/scheduleManager.ts +1 -0
  227. package/src/summarizer.ts +6 -6
  228. package/src/summarizerClientElection.ts +1 -1
  229. package/src/summarizerHeuristics.ts +0 -3
  230. package/src/summarizerTypes.ts +20 -7
  231. package/src/summaryFormat.ts +5 -3
  232. package/src/summaryGenerator.ts +3 -2
  233. package/src/summaryManager.ts +18 -7
  234. package/dist/batchManager.d.ts +0 -37
  235. package/dist/batchManager.d.ts.map +0 -1
  236. package/dist/batchManager.js.map +0 -1
  237. package/lib/batchManager.d.ts +0 -37
  238. package/lib/batchManager.d.ts.map +0 -1
  239. package/lib/batchManager.js.map +0 -1
package/src/dataStore.ts CHANGED
@@ -7,7 +7,12 @@ import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { assert, unreachableCase } from "@fluidframework/common-utils";
8
8
  import { AttachState } from "@fluidframework/container-definitions";
9
9
  import { UsageError } from "@fluidframework/container-utils";
10
- import { IRequest, IResponse } from "@fluidframework/core-interfaces";
10
+ import {
11
+ FluidObject,
12
+ IFluidHandle,
13
+ IRequest,
14
+ IResponse,
15
+ } from "@fluidframework/core-interfaces";
11
16
  import { AliasResult, IDataStore, IFluidDataStoreChannel } from "@fluidframework/runtime-definitions";
12
17
  import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
13
18
  import { ContainerRuntime } from "./containerRuntime";
@@ -151,6 +156,13 @@ class DataStore implements IDataStore {
151
156
  return this.fluidDataStoreChannel.request(request);
152
157
  }
153
158
 
159
+ /**
160
+ * {@inheritDoc @fluidframework/runtime-definitions#IDataStore.entryPoint}
161
+ */
162
+ get entryPoint(): IFluidHandle<FluidObject> | undefined {
163
+ return this.fluidDataStoreChannel.entryPoint;
164
+ }
165
+
154
166
  constructor(
155
167
  private readonly fluidDataStoreChannel: IFluidDataStoreChannel,
156
168
  private readonly internalId: string,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
6
+ import { IDisposable, ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
7
7
  import {
8
8
  FluidObject,
9
9
  IRequest,
@@ -42,7 +42,6 @@ import {
42
42
  CreateChildSummarizerNodeFn,
43
43
  CreateChildSummarizerNodeParam,
44
44
  FluidDataStoreRegistryEntry,
45
- gcBlobKey,
46
45
  IAttachMessage,
47
46
  IFluidDataStoreChannel,
48
47
  IFluidDataStoreContext,
@@ -51,7 +50,6 @@ import {
51
50
  IFluidDataStoreRegistry,
52
51
  IGarbageCollectionData,
53
52
  IGarbageCollectionDetailsBase,
54
- IGarbageCollectionSummaryDetails,
55
53
  IInboundSignalMessage,
56
54
  IProvideFluidDataStoreFactory,
57
55
  ISummarizeInternalResult,
@@ -60,14 +58,24 @@ import {
60
58
  SummarizeInternalFn,
61
59
  ITelemetryContext,
62
60
  } from "@fluidframework/runtime-definitions";
63
- import { addBlobToSummary, convertSummaryTreeToITree } from "@fluidframework/runtime-utils";
61
+ import {
62
+ addBlobToSummary,
63
+ convertSummaryTreeToITree,
64
+ packagePathToTelemetryProperty,
65
+ } from "@fluidframework/runtime-utils";
64
66
  import {
65
67
  ChildLogger,
68
+ loggerToMonitoringContext,
66
69
  LoggingError,
70
+ MonitoringContext,
67
71
  TelemetryDataTag,
68
72
  ThresholdCounter,
69
73
  } from "@fluidframework/telemetry-utils";
70
- import { DataProcessingError } from "@fluidframework/container-utils";
74
+ import {
75
+ DataCorruptionError,
76
+ DataProcessingError,
77
+ extractSafePropertiesFromMessage,
78
+ } from "@fluidframework/container-utils";
71
79
 
72
80
  import { ContainerRuntime } from "./containerRuntime";
73
81
  import {
@@ -79,6 +87,8 @@ import {
79
87
  getAttributesFormatVersion,
80
88
  getFluidDataStoreAttributes,
81
89
  } from "./summaryFormat";
90
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
91
+ import { summarizerClientType } from "./summarizerClientElection";
82
92
 
83
93
  function createAttributes(
84
94
  pkg: readonly string[],
@@ -117,7 +127,6 @@ export interface IFluidDataStoreContextProps {
117
127
  readonly storage: IDocumentStorageService;
118
128
  readonly scope: FluidObject;
119
129
  readonly createSummarizerNodeFn: CreateChildSummarizerNodeFn;
120
- readonly writeGCDataAtRoot: boolean;
121
130
  readonly pkg?: Readonly<string[]>;
122
131
  }
123
132
 
@@ -135,7 +144,7 @@ export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContext
135
144
 
136
145
  /** Properties necessary for creating a remote FluidDataStoreContext */
137
146
  export interface IRemoteFluidDataStoreContextProps extends IFluidDataStoreContextProps {
138
- readonly snapshotTree: ISnapshotTree | string | undefined;
147
+ readonly snapshotTree: ISnapshotTree | undefined;
139
148
  readonly getBaseGCDetails: () => Promise<IGarbageCollectionDetailsBase | undefined>;
140
149
  }
141
150
 
@@ -193,6 +202,15 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
193
202
  private _disposed = false;
194
203
  public get disposed() { return this._disposed; }
195
204
 
205
+ /**
206
+ * Tombstone is a temporary feature that prevents a data store from sending / receiving ops, signals and from
207
+ * loading.
208
+ */
209
+ private _tombstoned = false;
210
+ public get tombstoned() { return this._tombstoned; }
211
+ /** If true, throw an error when a tombstone data store is used. */
212
+ private readonly throwOnTombstoneUsage: boolean;
213
+
196
214
  public get attachState(): AttachState {
197
215
  return this._attachState;
198
216
  }
@@ -235,19 +253,18 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
235
253
  protected _attachState: AttachState;
236
254
  private _isInMemoryRoot: boolean = false;
237
255
  protected readonly summarizerNode: ISummarizerNodeWithGC;
238
- private readonly subLogger: ITelemetryLogger;
256
+ private readonly mc: MonitoringContext;
239
257
  private readonly thresholdOpsCounter: ThresholdCounter;
240
258
  private static readonly pendingOpsCountThreshold = 1000;
241
259
 
242
- // The used state of this node as per the last GC run. This is used to update the used state of the channel
260
+ // The used routes of this node as per the last GC run. This is used to update the used routes of the channel
243
261
  // if it realizes after GC is run.
244
- private lastUsedState: { usedRoutes: string[]; gcTimestamp?: number; } | undefined;
262
+ private lastUsedRoutes: string[] | undefined;
245
263
 
246
264
  public readonly id: string;
247
265
  private readonly _containerRuntime: ContainerRuntime;
248
266
  public readonly storage: IDocumentStorageService;
249
267
  public readonly scope: FluidObject;
250
- private readonly writeGCDataAtRoot: boolean;
251
268
  protected pkg?: readonly string[];
252
269
 
253
270
  constructor(
@@ -263,7 +280,6 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
263
280
  this.id = props.id;
264
281
  this.storage = props.storage;
265
282
  this.scope = props.scope;
266
- this.writeGCDataAtRoot = props.writeGCDataAtRoot;
267
283
  this.pkg = props.pkg;
268
284
 
269
285
  // URIs use slashes as delimiters. Handles use URIs.
@@ -291,8 +307,13 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
291
307
  async () => this.getBaseGCDetails(),
292
308
  );
293
309
 
294
- this.subLogger = ChildLogger.create(this.logger, "FluidDataStoreContext");
295
- this.thresholdOpsCounter = new ThresholdCounter(FluidDataStoreContext.pendingOpsCountThreshold, this.subLogger);
310
+ this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "FluidDataStoreContext"));
311
+ this.thresholdOpsCounter = new ThresholdCounter(FluidDataStoreContext.pendingOpsCountThreshold, this.mc.logger);
312
+
313
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
314
+ this.throwOnTombstoneUsage =
315
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
316
+ this.clientDetails.type !== summarizerClientType;
296
317
  }
297
318
 
298
319
  public dispose(): void {
@@ -310,6 +331,14 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
310
331
  }
311
332
  }
312
333
 
334
+ public setTombstone(tombstone: boolean) {
335
+ if (this.tombstoned === tombstone) {
336
+ return;
337
+ }
338
+
339
+ this._tombstoned = tombstone;
340
+ }
341
+
313
342
  private rejectDeferredRealize(reason: string, packageName?: string): never {
314
343
  throw new LoggingError(reason, { packageName: { value: packageName, tag: TelemetryDataTag.CodeArtifact } });
315
344
  }
@@ -386,7 +415,8 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
386
415
  * its new client ID when we are connecting or connected.
387
416
  */
388
417
  public setConnectionState(connected: boolean, clientId?: string) {
389
- this.verifyNotClosed();
418
+ // ConnectionState should not fail in tombstone mode as this is internally run
419
+ this.verifyNotClosed("setConnectionState", false /* checkTombstone */);
390
420
 
391
421
  // Connection events are ignored if the store is not yet loaded
392
422
  if (!this.loaded) {
@@ -400,7 +430,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
400
430
  }
401
431
 
402
432
  public process(messageArg: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void {
403
- this.verifyNotClosed();
433
+ this.verifyNotClosed("process", true, extractSafePropertiesFromMessage(messageArg));
404
434
 
405
435
  const innerContents = messageArg.contents as FluidDataStoreMessage;
406
436
  const message = {
@@ -422,7 +452,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
422
452
  }
423
453
 
424
454
  public processSignal(message: IInboundSignalMessage, local: boolean): void {
425
- this.verifyNotClosed();
455
+ this.verifyNotClosed("processSignal");
426
456
 
427
457
  // Signals are ignored if the store is not yet loaded
428
458
  if (!this.loaded) {
@@ -474,11 +504,6 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
474
504
  const attributes = createAttributes(pkg, isRoot);
475
505
  addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
476
506
 
477
- // Add GC data to the summary if it's not written at the root.
478
- if (!this.writeGCDataAtRoot) {
479
- addBlobToSummary(summarizeResult, gcBlobKey, JSON.stringify(this.summarizerNode.getGCSummaryDetails()));
480
- }
481
-
482
507
  // If we are not referenced, mark the summary tree as unreferenced. Also, update unreferenced blob
483
508
  // size in the summary stats with the blobs size of this data store.
484
509
  if (!this.summarizerNode.isReferenced()) {
@@ -532,21 +557,17 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
532
557
  * 5. To update the timestamp when this data store or any children are marked as unreferenced.
533
558
  *
534
559
  * @param usedRoutes - The routes that are used in this data store.
535
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node becomes unreferenced
536
- * as part of this GC run, this should be used to update the time when it happens.
537
560
  */
538
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
561
+ public updateUsedRoutes(usedRoutes: string[]) {
539
562
  // Update the used routes in this data store's summarizer node.
540
- this.summarizerNode.updateUsedRoutes(usedRoutes, gcTimestamp);
563
+ this.summarizerNode.updateUsedRoutes(usedRoutes);
541
564
 
542
565
  /**
543
- * If the data store has not been realized yet, we need this used state to update the used state of the channel
544
- * when it realizes. It's safe to keep only the last used state because if something changes because of this GC
545
- * run, the data store will be immediately realized as part of the summary that follows GC. For example, if a
546
- * child's reference state changes, the gcTimestamp has to be used to update its unreferencedTimestamp. Since
547
- * it will result in a change in this data store's used routes, it will be realized to regenerate its summary.
566
+ * Store the used routes to update the channel if the data store is not loaded yet. If the used routes changed
567
+ * since the previous run, the data store will be loaded during summarize since the used state changed. So, it's
568
+ * safe to only store the last used routes.
548
569
  */
549
- this.lastUsedState = { usedRoutes, gcTimestamp };
570
+ this.lastUsedRoutes = usedRoutes;
550
571
 
551
572
  // If we are loaded, call the channel so it can update the used routes of the child contexts.
552
573
  // If we are not loaded, we will update this when we are realized.
@@ -575,16 +596,16 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
575
596
  assert(this.loaded, 0x144 /* "Channel should be loaded when updating used routes" */);
576
597
  assert(this.channel !== undefined, 0x145 /* "Channel should be present when data store is loaded" */);
577
598
 
578
- // If there is no lastUsedState, GC has not run up until this point.
579
- if (this.lastUsedState === undefined) {
599
+ // If there is no lastUsedRoutes, GC has not run up until this point.
600
+ if (this.lastUsedRoutes === undefined) {
580
601
  return;
581
602
  }
582
603
 
583
604
  // Remove the route to this data store, if it exists.
584
- const usedChannelRoutes = this.lastUsedState.usedRoutes.filter(
605
+ const usedChannelRoutes = this.lastUsedRoutes.filter(
585
606
  (id: string) => { return id !== "/" && id !== ""; },
586
607
  );
587
- this.channel.updateUsedRoutes(usedChannelRoutes, this.lastUsedState.gcTimestamp);
608
+ this.channel.updateUsedRoutes(usedChannelRoutes);
588
609
  }
589
610
 
590
611
  /**
@@ -596,7 +617,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
596
617
  }
597
618
 
598
619
  public submitMessage(type: string, content: any, localOpMetadata: unknown): void {
599
- this.verifyNotClosed();
620
+ this.verifyNotClosed("submitMessage");
600
621
  assert(!!this.channel, 0x146 /* "Channel must exist when submitting message" */);
601
622
  const fluidDataStoreContent: FluidDataStoreMessage = {
602
623
  content,
@@ -618,7 +639,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
618
639
  *
619
640
  */
620
641
  public setChannelDirty(address: string): void {
621
- this.verifyNotClosed();
642
+ this.verifyNotClosed("setChannelDirty");
622
643
 
623
644
  // Get the latest sequence number.
624
645
  const latestSequenceNumber = this.deltaManager.lastSequenceNumber;
@@ -633,7 +654,8 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
633
654
  }
634
655
 
635
656
  public submitSignal(type: string, content: any) {
636
- this.verifyNotClosed();
657
+ this.verifyNotClosed("submitSignal");
658
+
637
659
  assert(!!this.channel, 0x147 /* "Channel must exist on submitting signal" */);
638
660
  return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
639
661
  }
@@ -718,11 +740,6 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
718
740
  this._isInMemoryRoot = true;
719
741
  }
720
742
 
721
- /**
722
- * @deprecated Renamed to `{@link FluidDataStoreContext.getBaseGCDetails}()`.
723
- */
724
- public abstract getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails>;
725
-
726
743
  public abstract getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
727
744
 
728
745
  public reSubmit(contents: any, localOpMetadata: unknown) {
@@ -751,9 +768,30 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
751
768
  return this.channel.applyStashedOp(innerContents.content);
752
769
  }
753
770
 
754
- private verifyNotClosed() {
771
+ private verifyNotClosed(callSite: string, checkTombstone = true, safeTelemetryProps: ITelemetryProperties = {}) {
755
772
  if (this._disposed) {
756
- throw new Error("Context is closed");
773
+ throw new Error(`Context is closed! Call site [${callSite}]`);
774
+ }
775
+
776
+ if (checkTombstone && this.tombstoned) {
777
+ const messageString = `Context is tombstoned! Call site [${callSite}]`;
778
+ const error = new DataCorruptionError(messageString, {
779
+ errorMessage: messageString,
780
+ ...safeTelemetryProps,
781
+ });
782
+
783
+ // Always log an error when tombstoned data store is used. However, throw an error only if
784
+ // throwOnTombstoneUsage is set.
785
+ this.mc.logger.sendErrorEvent({
786
+ eventName: "GC_Tombstone_DataStore_Changed",
787
+ callSite,
788
+ pkg: packagePathToTelemetryProperty(this.pkg),
789
+ }, error);
790
+ // Always log an error when tombstoned data store is used. However, throw an error only if
791
+ // throwOnTombstoneUsage is set and the client is not a summarizer.
792
+ if (this.throwOnTombstoneUsage) {
793
+ throw error;
794
+ }
757
795
  }
758
796
  }
759
797
 
@@ -779,7 +817,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
779
817
  }
780
818
 
781
819
  export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
782
- private readonly initSnapshotValue: ISnapshotTree | string | undefined;
820
+ private readonly initSnapshotValue: ISnapshotTree | undefined;
783
821
  private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
784
822
 
785
823
  constructor(props: IRemoteFluidDataStoreContextProps) {
@@ -797,28 +835,20 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
797
835
  this.baseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
798
836
  return (await props.getBaseGCDetails()) ?? {};
799
837
  });
838
+
839
+ if (props.snapshotTree !== undefined) {
840
+ this.summarizerNode.updateBaseSummaryState(props.snapshotTree);
841
+ }
800
842
  }
801
843
 
802
844
  private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
803
- let tree: ISnapshotTree | undefined;
845
+ let tree = this.initSnapshotValue;
804
846
  let isRootDataStore = true;
805
847
 
806
- if (typeof this.initSnapshotValue === "string") {
807
- const commit = (await this.storage.getVersions(this.initSnapshotValue, 1))[0];
808
- tree = await this.storage.getSnapshotTree(commit) ?? undefined;
809
- } else {
810
- tree = this.initSnapshotValue;
811
- }
812
-
813
- const localReadAndParse = async <T>(id: string) => readAndParse<T>(this.storage, id);
814
- if (tree) {
815
- tree = await this.summarizerNode.loadBaseSummary(tree, localReadAndParse);
816
- }
817
-
818
848
  if (!!tree && tree.blobs[dataStoreAttributesBlobName] !== undefined) {
819
849
  // Need to get through snapshot and use that to populate extraBlobs
820
850
  const attributes =
821
- await localReadAndParse<ReadFluidDataStoreAttributes>(tree.blobs[dataStoreAttributesBlobName]);
851
+ await readAndParse<ReadFluidDataStoreAttributes>(this.storage, tree.blobs[dataStoreAttributesBlobName]);
822
852
 
823
853
  let pkgFromSnapshot: string[];
824
854
  // Use the snapshotFormatVersion to determine how the pkg is encoded in the snapshot.
@@ -859,13 +889,6 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
859
889
  return this.initialSnapshotDetailsP;
860
890
  }
861
891
 
862
- /**
863
- * @deprecated Renamed to {@link RemoteFluidDataStoreContext.getBaseGCDetails}.
864
- */
865
- public async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
866
- return this.getBaseGCDetails();
867
- }
868
-
869
892
  public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
870
893
  return this.baseGCDetailsP;
871
894
  }
@@ -974,14 +997,6 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
974
997
  };
975
998
  }
976
999
 
977
- /**
978
- * @deprecated Renamed to {@link LocalFluidDataStoreContextBase.getBaseGCDetails}.
979
- */
980
- public async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
981
- // Local data store does not have initial summary.
982
- return {};
983
- }
984
-
985
1000
  public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
986
1001
  // Local data store does not have initial summary.
987
1002
  return {};
@@ -1033,6 +1048,15 @@ export class LocalDetachedFluidDataStoreContext
1033
1048
 
1034
1049
  super.bindRuntime(dataStoreChannel);
1035
1050
 
1051
+ // Load the handle to the data store's entryPoint to make sure that for a detached data store, the entryPoint
1052
+ // initialization function is called before the data store gets attached and potentially connected to the
1053
+ // delta stream, so it gets a chance to do things while the data store is still "purely local".
1054
+ // This preserves the behavior from before we introduced entryPoints, where the instantiateDataStore method
1055
+ // of data store factories tends to construct the data object (at least kick off an async method that returns
1056
+ // it); that code moved to the entryPoint initialization function, so we want to ensure it still executes
1057
+ // before the data store is attached.
1058
+ await dataStoreChannel.entryPoint?.get();
1059
+
1036
1060
  if (await this.isRoot()) {
1037
1061
  dataStoreChannel.makeVisibleAndAttachGraph();
1038
1062
  }
@@ -87,7 +87,7 @@ import { FluidDataStoreContext, LocalFluidDataStoreContext } from "./dataStoreCo
87
87
  return undefined;
88
88
  }
89
89
 
90
- return this._contexts.get(id) as LocalFluidDataStoreContext;
90
+ return context as LocalFluidDataStoreContext;
91
91
  }
92
92
 
93
93
  /**
package/src/dataStores.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger, ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
6
+ import { ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
7
7
  import { DataCorruptionError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
8
8
  import { IFluidHandle } from "@fluidframework/core-interfaces";
9
9
  import { FluidObjectHandle } from "@fluidframework/datastore";
@@ -32,10 +32,12 @@ import {
32
32
  convertSnapshotTreeToSummaryTree,
33
33
  convertToSummaryTree,
34
34
  create404Response,
35
+ createResponseError,
35
36
  responseToException,
37
+ packagePathToTelemetryProperty,
36
38
  SummaryTreeBuilder,
37
39
  } from "@fluidframework/runtime-utils";
38
- import { ChildLogger, LoggingError, TelemetryDataTag } from "@fluidframework/telemetry-utils";
40
+ import { ChildLogger, loggerToMonitoringContext, LoggingError, MonitoringContext, TelemetryDataTag } from "@fluidframework/telemetry-utils";
39
41
  import { AttachState } from "@fluidframework/container-definitions";
40
42
  import { BlobCacheStorageService, buildSnapshotTree } from "@fluidframework/driver-utils";
41
43
  import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
@@ -53,6 +55,8 @@ import {
53
55
  import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
54
56
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
55
57
  import { GCNodeType } from "./garbageCollection";
58
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
59
+ import { summarizerClientType } from "./summarizerClientElection";
56
60
 
57
61
  type PendingAliasResolve = (success: boolean) => void;
58
62
 
@@ -66,7 +70,7 @@ export class DataStores implements IDisposable {
66
70
  // 0.24 back-compat attachingBeforeSummary
67
71
  public readonly attachOpFiredForDataStore = new Set<string>();
68
72
 
69
- private readonly logger: ITelemetryLogger;
73
+ private readonly mc: MonitoringContext;
70
74
 
71
75
  private readonly disposeOnce = new Lazy<void>(() => this.contexts.dispose());
72
76
 
@@ -80,6 +84,8 @@ export class DataStores implements IDisposable {
80
84
  // Stores the ids of new data stores between two GC runs. This is used to notify the garbage collector of new
81
85
  // root data stores that are added.
82
86
  private dataStoresSinceLastGC: string[] = [];
87
+ /** If true, throw an error when a tombstone data store is retrieved. */
88
+ private readonly throwOnTombstoneUsage: boolean;
83
89
  // The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
84
90
  // the container runtime to other nodes.
85
91
  private readonly containerRuntimeHandle: IFluidHandle;
@@ -97,10 +103,9 @@ export class DataStores implements IDisposable {
97
103
  private readonly gcNodeUpdated: (
98
104
  nodePath: string, timestampMs: number, packagePath?: readonly string[]) => void,
99
105
  private readonly aliasMap: Map<string, string>,
100
- private readonly writeGCDataAtRoot: boolean,
101
106
  private readonly contexts: DataStoreContexts = new DataStoreContexts(baseLogger),
102
107
  ) {
103
- this.logger = ChildLogger.create(baseLogger);
108
+ this.mc = loggerToMonitoringContext(ChildLogger.create(baseLogger));
104
109
  this.containerRuntimeHandle = new FluidObjectHandle(this.runtime, "/", this.runtime.IFluidHandleContext);
105
110
 
106
111
  const baseGCDetailsP = new LazyPromise(async () => {
@@ -111,6 +116,10 @@ export class DataStores implements IDisposable {
111
116
  const baseGCDetails = await baseGCDetailsP;
112
117
  return baseGCDetails.get(dataStoreId);
113
118
  };
119
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
120
+ this.throwOnTombstoneUsage =
121
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
122
+ this.runtime.clientDetails.type !== summarizerClientType;
114
123
 
115
124
  // Extract stores stored inside the snapshot
116
125
  const fluidDataStores = new Map<string, ISnapshotTree>();
@@ -142,7 +151,6 @@ export class DataStores implements IDisposable {
142
151
  key,
143
152
  { type: CreateSummarizerNodeSource.FromSummary },
144
153
  ),
145
- writeGCDataAtRoot: this.writeGCDataAtRoot,
146
154
  });
147
155
  } else {
148
156
  if (typeof value !== "object") {
@@ -162,7 +170,6 @@ export class DataStores implements IDisposable {
162
170
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(key),
163
171
  snapshotTree,
164
172
  isRootDataStore: undefined,
165
- writeGCDataAtRoot: this.writeGCDataAtRoot,
166
173
  });
167
174
  }
168
175
  this.contexts.addBoundOrRemoted(dataStoreContext);
@@ -247,7 +254,6 @@ export class DataStores implements IDisposable {
247
254
  },
248
255
  },
249
256
  ),
250
- writeGCDataAtRoot: this.writeGCDataAtRoot,
251
257
  pkg,
252
258
  });
253
259
 
@@ -283,7 +289,7 @@ export class DataStores implements IDisposable {
283
289
 
284
290
  const context = this.contexts.get(aliasMessage.internalId);
285
291
  if (context === undefined) {
286
- this.logger.sendErrorEvent({
292
+ this.mc.logger.sendErrorEvent({
287
293
  eventName: "AliasFluidDataStoreNotFound",
288
294
  fluidDataStoreId: aliasMessage.internalId,
289
295
  });
@@ -351,7 +357,6 @@ export class DataStores implements IDisposable {
351
357
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
352
358
  snapshotTree: undefined,
353
359
  isRootDataStore: isRoot,
354
- writeGCDataAtRoot: this.writeGCDataAtRoot,
355
360
  });
356
361
  this.contexts.addUnbound(context);
357
362
  return context;
@@ -372,7 +377,6 @@ export class DataStores implements IDisposable {
372
377
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
373
378
  snapshotTree: undefined,
374
379
  isRootDataStore: false,
375
- writeGCDataAtRoot: this.writeGCDataAtRoot,
376
380
  createProps: props,
377
381
  });
378
382
  this.contexts.addUnbound(context);
@@ -425,14 +429,31 @@ export class DataStores implements IDisposable {
425
429
  );
426
430
  }
427
431
 
428
- public async getDataStore(id: string, wait: boolean): Promise<FluidDataStoreContext> {
432
+ public async getDataStore(id: string, wait: boolean, viaHandle: boolean): Promise<FluidDataStoreContext> {
429
433
  const context = await this.contexts.getBoundOrRemoted(id, wait);
434
+ const request = { url: id };
430
435
  if (context === undefined) {
431
436
  // The requested data store does not exits. Throw a 404 response exception.
432
- const request = { url: id };
433
437
  throw responseToException(create404Response(request), request);
434
438
  }
435
439
 
440
+ if (context.tombstoned) {
441
+ // The requested data store is removed by gc. Create a 404 gc response exception.
442
+ const error = responseToException(createResponseError(404, "Datastore removed by gc", request), request);
443
+ // Note: if a user writes a request to look like it's viaHandle, we will also send this telemetry event
444
+ this.mc.logger.sendErrorEvent({
445
+ eventName: "GC_Tombstone_DataStore_Requested",
446
+ url: request.url,
447
+ pkg: packagePathToTelemetryProperty(context.isLoaded ? context.packagePath : undefined),
448
+ viaHandle,
449
+ }, error);
450
+ // Always log an error when tombstoned data store is used. However, throw an error only if
451
+ // throwOnTombstoneUsage is set.
452
+ if (this.throwOnTombstoneUsage) {
453
+ throw error;
454
+ }
455
+ }
456
+
436
457
  return context;
437
458
  }
438
459
 
@@ -441,7 +462,7 @@ export class DataStores implements IDisposable {
441
462
  if (!context) {
442
463
  // Attach message may not have been processed yet
443
464
  assert(!local, 0x163 /* "Missing datastore for local signal" */);
444
- this.logger.sendTelemetryEvent({
465
+ this.mc.logger.sendTelemetryEvent({
445
466
  eventName: "SignalFluidDataStoreNotFound",
446
467
  fluidDataStoreId: {
447
468
  value: address,
@@ -459,7 +480,7 @@ export class DataStores implements IDisposable {
459
480
  try {
460
481
  context.setConnectionState(connected, clientId);
461
482
  } catch (error) {
462
- this.logger.sendErrorEvent({
483
+ this.mc.logger.sendErrorEvent({
463
484
  eventName: "SetConnectionStateError",
464
485
  clientId,
465
486
  fluidDataStore,
@@ -595,30 +616,35 @@ export class DataStores implements IDisposable {
595
616
  /**
596
617
  * After GC has run, called to notify this Container's data stores of routes that are used in it.
597
618
  * @param usedRoutes - The routes that are used in all data stores in this Container.
598
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
599
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
600
619
  */
601
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
620
+ public updateUsedRoutes(usedRoutes: string[]) {
602
621
  // Get a map of data store ids to routes used in it.
603
622
  const usedDataStoreRoutes = unpackChildNodesUsedRoutes(usedRoutes);
604
623
 
605
624
  // Verify that the used routes are correct.
606
625
  for (const [id] of usedDataStoreRoutes) {
607
626
  assert(this.contexts.has(id), 0x167 /* "Used route does not belong to any known data store" */);
627
+
628
+ // Revive datastores regardless of whether or not tombstone the tombstone flag is flipped
629
+ const dataStore = this.contexts.get(id);
630
+ assert(dataStore !== undefined, 0x46e /* No data store retrieved with specified id */);
631
+ dataStore.setTombstone(false /* tombstone */);
608
632
  }
609
633
 
610
634
  // Update the used routes in each data store. Used routes is empty for unused data stores.
611
635
  for (const [contextId, context] of this.contexts) {
612
- context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? [], gcTimestamp);
636
+ context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? []);
613
637
  }
614
638
  }
615
639
 
616
640
  /**
617
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
618
- * scenarios with accessing deleted content.
641
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
642
+ * tombstones.
619
643
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
644
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
645
+ * are deleted.
620
646
  */
621
- public deleteUnusedRoutes(unusedRoutes: string[]) {
647
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
622
648
  for (const route of unusedRoutes) {
623
649
  const pathParts = route.split("/");
624
650
  // Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
@@ -628,6 +654,19 @@ export class DataStores implements IDisposable {
628
654
  }
629
655
  const dataStoreId = pathParts[1];
630
656
  assert(this.contexts.has(dataStoreId), 0x2d7 /* No data store with specified id */);
657
+
658
+ /**
659
+ * When running GC in tombstone mode, datastore contexts are tombstoned. Tombstoned datastore contexts
660
+ * enable testing scenarios with accessing deleted content without actually deleting content from
661
+ * summaries.
662
+ */
663
+ if (tombstone) {
664
+ const dataStore = this.contexts.get(dataStoreId);
665
+ assert(dataStore !== undefined, 0x442 /* No data store retrieved with specified id */);
666
+ dataStore.setTombstone(true /* tombstone */);
667
+ continue;
668
+ }
669
+
631
670
  // Delete the contexts of unused data stores.
632
671
  this.contexts.delete(dataStoreId);
633
672
  // Delete the summarizer node of the unused data stores.