@fluidframework/container-runtime 2.0.0-dev.1.4.5.105745 → 2.0.0-dev.2.2.0.111723

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 (168) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/batchManager.d.ts +11 -6
  3. package/dist/batchManager.d.ts.map +1 -1
  4. package/dist/batchManager.js +23 -13
  5. package/dist/batchManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +74 -20
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +190 -137
  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 +50 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +36 -19
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +207 -121
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  28. package/dist/gcSweepReadyUsageDetection.js +3 -12
  29. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  30. package/dist/index.d.ts +4 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/opCompressor.d.ts +18 -0
  35. package/dist/opCompressor.d.ts.map +1 -0
  36. package/dist/opCompressor.js +50 -0
  37. package/dist/opCompressor.js.map +1 -0
  38. package/dist/opDecompressor.d.ts +20 -0
  39. package/dist/opDecompressor.d.ts.map +1 -0
  40. package/dist/opDecompressor.js +72 -0
  41. package/dist/opDecompressor.js.map +1 -0
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/pendingStateManager.d.ts +6 -26
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +42 -62
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/runningSummarizer.d.ts +3 -2
  50. package/dist/runningSummarizer.d.ts.map +1 -1
  51. package/dist/runningSummarizer.js +10 -3
  52. package/dist/runningSummarizer.js.map +1 -1
  53. package/dist/scheduleManager.js.map +1 -1
  54. package/dist/summarizer.js +7 -2
  55. package/dist/summarizer.js.map +1 -1
  56. package/dist/summarizerClientElection.js +1 -1
  57. package/dist/summarizerClientElection.js.map +1 -1
  58. package/dist/summarizerHeuristics.d.ts.map +1 -1
  59. package/dist/summarizerHeuristics.js +0 -3
  60. package/dist/summarizerHeuristics.js.map +1 -1
  61. package/dist/summarizerTypes.d.ts +19 -2
  62. package/dist/summarizerTypes.d.ts.map +1 -1
  63. package/dist/summarizerTypes.js.map +1 -1
  64. package/dist/summaryFormat.d.ts +4 -2
  65. package/dist/summaryFormat.d.ts.map +1 -1
  66. package/dist/summaryFormat.js.map +1 -1
  67. package/dist/summaryGenerator.d.ts.map +1 -1
  68. package/dist/summaryGenerator.js +3 -2
  69. package/dist/summaryGenerator.js.map +1 -1
  70. package/dist/summaryManager.d.ts.map +1 -1
  71. package/dist/summaryManager.js +10 -6
  72. package/dist/summaryManager.js.map +1 -1
  73. package/garbageCollection.md +27 -22
  74. package/lib/batchManager.d.ts +11 -6
  75. package/lib/batchManager.d.ts.map +1 -1
  76. package/lib/batchManager.js +23 -13
  77. package/lib/batchManager.js.map +1 -1
  78. package/lib/containerRuntime.d.ts +74 -20
  79. package/lib/containerRuntime.d.ts.map +1 -1
  80. package/lib/containerRuntime.js +189 -136
  81. package/lib/containerRuntime.js.map +1 -1
  82. package/lib/dataStore.d.ts.map +1 -1
  83. package/lib/dataStore.js +6 -0
  84. package/lib/dataStore.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +14 -21
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js +75 -61
  88. package/lib/dataStoreContext.js.map +1 -1
  89. package/lib/dataStoreContexts.js +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +11 -10
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js +53 -23
  94. package/lib/dataStores.js.map +1 -1
  95. package/lib/garbageCollection.d.ts +36 -19
  96. package/lib/garbageCollection.d.ts.map +1 -1
  97. package/lib/garbageCollection.js +208 -122
  98. package/lib/garbageCollection.js.map +1 -1
  99. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  100. package/lib/gcSweepReadyUsageDetection.js +3 -12
  101. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  102. package/lib/index.d.ts +4 -6
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +2 -4
  105. package/lib/index.js.map +1 -1
  106. package/lib/opCompressor.d.ts +18 -0
  107. package/lib/opCompressor.d.ts.map +1 -0
  108. package/lib/opCompressor.js +46 -0
  109. package/lib/opCompressor.js.map +1 -0
  110. package/lib/opDecompressor.d.ts +20 -0
  111. package/lib/opDecompressor.d.ts.map +1 -0
  112. package/lib/opDecompressor.js +68 -0
  113. package/lib/opDecompressor.js.map +1 -0
  114. package/lib/packageVersion.d.ts +1 -1
  115. package/lib/packageVersion.js +1 -1
  116. package/lib/packageVersion.js.map +1 -1
  117. package/lib/pendingStateManager.d.ts +6 -26
  118. package/lib/pendingStateManager.d.ts.map +1 -1
  119. package/lib/pendingStateManager.js +42 -62
  120. package/lib/pendingStateManager.js.map +1 -1
  121. package/lib/runningSummarizer.d.ts +3 -2
  122. package/lib/runningSummarizer.d.ts.map +1 -1
  123. package/lib/runningSummarizer.js +10 -3
  124. package/lib/runningSummarizer.js.map +1 -1
  125. package/lib/scheduleManager.js.map +1 -1
  126. package/lib/summarizer.js +7 -2
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerClientElection.js +1 -1
  129. package/lib/summarizerClientElection.js.map +1 -1
  130. package/lib/summarizerHeuristics.d.ts.map +1 -1
  131. package/lib/summarizerHeuristics.js +0 -3
  132. package/lib/summarizerHeuristics.js.map +1 -1
  133. package/lib/summarizerTypes.d.ts +19 -2
  134. package/lib/summarizerTypes.d.ts.map +1 -1
  135. package/lib/summarizerTypes.js.map +1 -1
  136. package/lib/summaryFormat.d.ts +4 -2
  137. package/lib/summaryFormat.d.ts.map +1 -1
  138. package/lib/summaryFormat.js.map +1 -1
  139. package/lib/summaryGenerator.d.ts.map +1 -1
  140. package/lib/summaryGenerator.js +3 -2
  141. package/lib/summaryGenerator.js.map +1 -1
  142. package/lib/summaryManager.d.ts.map +1 -1
  143. package/lib/summaryManager.js +10 -6
  144. package/lib/summaryManager.js.map +1 -1
  145. package/package.json +37 -63
  146. package/prettier.config.cjs +8 -0
  147. package/src/batchManager.ts +32 -15
  148. package/src/containerRuntime.ts +260 -156
  149. package/src/dataStore.ts +13 -1
  150. package/src/dataStoreContext.ts +100 -76
  151. package/src/dataStoreContexts.ts +1 -1
  152. package/src/dataStores.ts +61 -23
  153. package/src/garbageCollection.ts +257 -126
  154. package/src/gcSweepReadyUsageDetection.ts +2 -10
  155. package/src/index.ts +4 -4
  156. package/src/opCompressor.ts +59 -0
  157. package/src/opDecompressor.ts +82 -0
  158. package/src/packageVersion.ts +1 -1
  159. package/src/pendingStateManager.ts +57 -96
  160. package/src/runningSummarizer.ts +11 -3
  161. package/src/scheduleManager.ts +1 -0
  162. package/src/summarizer.ts +6 -6
  163. package/src/summarizerClientElection.ts +1 -1
  164. package/src/summarizerHeuristics.ts +0 -3
  165. package/src/summarizerTypes.ts +20 -7
  166. package/src/summaryFormat.ts +4 -2
  167. package/src/summaryGenerator.ts +3 -2
  168. package/src/summaryManager.ts +18 -7
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 "./garbageCollection";
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";
@@ -52,7 +54,8 @@ import {
52
54
  } from "./dataStoreContext";
53
55
  import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
54
56
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
55
- import { GCNodeType } from "./garbageCollection";
57
+ import { throwOnTombstoneUsageKey, GCNodeType } from "./garbageCollection";
58
+ import { summarizerClientType } from "./summarizerClientElection";
56
59
 
57
60
  type PendingAliasResolve = (success: boolean) => void;
58
61
 
@@ -66,7 +69,7 @@ export class DataStores implements IDisposable {
66
69
  // 0.24 back-compat attachingBeforeSummary
67
70
  public readonly attachOpFiredForDataStore = new Set<string>();
68
71
 
69
- private readonly logger: ITelemetryLogger;
72
+ private readonly mc: MonitoringContext;
70
73
 
71
74
  private readonly disposeOnce = new Lazy<void>(() => this.contexts.dispose());
72
75
 
@@ -80,6 +83,8 @@ export class DataStores implements IDisposable {
80
83
  // Stores the ids of new data stores between two GC runs. This is used to notify the garbage collector of new
81
84
  // root data stores that are added.
82
85
  private dataStoresSinceLastGC: string[] = [];
86
+ /** If true, throw an error when a tombstone data store is retrieved. */
87
+ private readonly throwOnTombstoneUsage: boolean;
83
88
  // The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
84
89
  // the container runtime to other nodes.
85
90
  private readonly containerRuntimeHandle: IFluidHandle;
@@ -97,10 +102,9 @@ export class DataStores implements IDisposable {
97
102
  private readonly gcNodeUpdated: (
98
103
  nodePath: string, timestampMs: number, packagePath?: readonly string[]) => void,
99
104
  private readonly aliasMap: Map<string, string>,
100
- private readonly writeGCDataAtRoot: boolean,
101
105
  private readonly contexts: DataStoreContexts = new DataStoreContexts(baseLogger),
102
106
  ) {
103
- this.logger = ChildLogger.create(baseLogger);
107
+ this.mc = loggerToMonitoringContext(ChildLogger.create(baseLogger));
104
108
  this.containerRuntimeHandle = new FluidObjectHandle(this.runtime, "/", this.runtime.IFluidHandleContext);
105
109
 
106
110
  const baseGCDetailsP = new LazyPromise(async () => {
@@ -111,6 +115,10 @@ export class DataStores implements IDisposable {
111
115
  const baseGCDetails = await baseGCDetailsP;
112
116
  return baseGCDetails.get(dataStoreId);
113
117
  };
118
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
119
+ this.throwOnTombstoneUsage =
120
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
121
+ this.runtime.clientDetails.type !== summarizerClientType;
114
122
 
115
123
  // Extract stores stored inside the snapshot
116
124
  const fluidDataStores = new Map<string, ISnapshotTree>();
@@ -142,7 +150,6 @@ export class DataStores implements IDisposable {
142
150
  key,
143
151
  { type: CreateSummarizerNodeSource.FromSummary },
144
152
  ),
145
- writeGCDataAtRoot: this.writeGCDataAtRoot,
146
153
  });
147
154
  } else {
148
155
  if (typeof value !== "object") {
@@ -162,7 +169,6 @@ export class DataStores implements IDisposable {
162
169
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(key),
163
170
  snapshotTree,
164
171
  isRootDataStore: undefined,
165
- writeGCDataAtRoot: this.writeGCDataAtRoot,
166
172
  });
167
173
  }
168
174
  this.contexts.addBoundOrRemoted(dataStoreContext);
@@ -247,7 +253,6 @@ export class DataStores implements IDisposable {
247
253
  },
248
254
  },
249
255
  ),
250
- writeGCDataAtRoot: this.writeGCDataAtRoot,
251
256
  pkg,
252
257
  });
253
258
 
@@ -283,7 +288,7 @@ export class DataStores implements IDisposable {
283
288
 
284
289
  const context = this.contexts.get(aliasMessage.internalId);
285
290
  if (context === undefined) {
286
- this.logger.sendErrorEvent({
291
+ this.mc.logger.sendErrorEvent({
287
292
  eventName: "AliasFluidDataStoreNotFound",
288
293
  fluidDataStoreId: aliasMessage.internalId,
289
294
  });
@@ -351,7 +356,6 @@ export class DataStores implements IDisposable {
351
356
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
352
357
  snapshotTree: undefined,
353
358
  isRootDataStore: isRoot,
354
- writeGCDataAtRoot: this.writeGCDataAtRoot,
355
359
  });
356
360
  this.contexts.addUnbound(context);
357
361
  return context;
@@ -372,7 +376,6 @@ export class DataStores implements IDisposable {
372
376
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
373
377
  snapshotTree: undefined,
374
378
  isRootDataStore: false,
375
- writeGCDataAtRoot: this.writeGCDataAtRoot,
376
379
  createProps: props,
377
380
  });
378
381
  this.contexts.addUnbound(context);
@@ -425,14 +428,31 @@ export class DataStores implements IDisposable {
425
428
  );
426
429
  }
427
430
 
428
- public async getDataStore(id: string, wait: boolean): Promise<FluidDataStoreContext> {
431
+ public async getDataStore(id: string, wait: boolean, viaHandle: boolean): Promise<FluidDataStoreContext> {
429
432
  const context = await this.contexts.getBoundOrRemoted(id, wait);
433
+ const request = { url: id };
430
434
  if (context === undefined) {
431
435
  // The requested data store does not exits. Throw a 404 response exception.
432
- const request = { url: id };
433
436
  throw responseToException(create404Response(request), request);
434
437
  }
435
438
 
439
+ if (context.tombstoned) {
440
+ // The requested data store is removed by gc. Create a 404 gc response exception.
441
+ const error = responseToException(createResponseError(404, "Datastore removed by gc", request), request);
442
+ // Note: if a user writes a request to look like it's viaHandle, we will also send this telemetry event
443
+ this.mc.logger.sendErrorEvent({
444
+ eventName: "GC_Tombstone_DataStore_Requested",
445
+ url: request.url,
446
+ pkg: packagePathToTelemetryProperty(context.isLoaded ? context.packagePath : undefined),
447
+ viaHandle,
448
+ }, error);
449
+ // Always log an error when tombstoned data store is used. However, throw an error only if
450
+ // throwOnTombstoneUsage is set.
451
+ if (this.throwOnTombstoneUsage) {
452
+ throw error;
453
+ }
454
+ }
455
+
436
456
  return context;
437
457
  }
438
458
 
@@ -441,7 +461,7 @@ export class DataStores implements IDisposable {
441
461
  if (!context) {
442
462
  // Attach message may not have been processed yet
443
463
  assert(!local, 0x163 /* "Missing datastore for local signal" */);
444
- this.logger.sendTelemetryEvent({
464
+ this.mc.logger.sendTelemetryEvent({
445
465
  eventName: "SignalFluidDataStoreNotFound",
446
466
  fluidDataStoreId: {
447
467
  value: address,
@@ -459,7 +479,7 @@ export class DataStores implements IDisposable {
459
479
  try {
460
480
  context.setConnectionState(connected, clientId);
461
481
  } catch (error) {
462
- this.logger.sendErrorEvent({
482
+ this.mc.logger.sendErrorEvent({
463
483
  eventName: "SetConnectionStateError",
464
484
  clientId,
465
485
  fluidDataStore,
@@ -595,30 +615,35 @@ export class DataStores implements IDisposable {
595
615
  /**
596
616
  * After GC has run, called to notify this Container's data stores of routes that are used in it.
597
617
  * @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
618
  */
601
- public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number) {
619
+ public updateUsedRoutes(usedRoutes: string[]) {
602
620
  // Get a map of data store ids to routes used in it.
603
621
  const usedDataStoreRoutes = unpackChildNodesUsedRoutes(usedRoutes);
604
622
 
605
623
  // Verify that the used routes are correct.
606
624
  for (const [id] of usedDataStoreRoutes) {
607
625
  assert(this.contexts.has(id), 0x167 /* "Used route does not belong to any known data store" */);
626
+
627
+ // Revive datastores regardless of whether or not tombstone the tombstone flag is flipped
628
+ const dataStore = this.contexts.get(id);
629
+ assert(dataStore !== undefined, 0x46e /* No data store retrieved with specified id */);
630
+ dataStore.setTombstone(false /* tombstone */);
608
631
  }
609
632
 
610
633
  // Update the used routes in each data store. Used routes is empty for unused data stores.
611
634
  for (const [contextId, context] of this.contexts) {
612
- context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? [], gcTimestamp);
635
+ context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? []);
613
636
  }
614
637
  }
615
638
 
616
639
  /**
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.
640
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
641
+ * tombstones.
619
642
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
643
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
644
+ * are deleted.
620
645
  */
621
- public deleteUnusedRoutes(unusedRoutes: string[]) {
646
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
622
647
  for (const route of unusedRoutes) {
623
648
  const pathParts = route.split("/");
624
649
  // Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
@@ -628,6 +653,19 @@ export class DataStores implements IDisposable {
628
653
  }
629
654
  const dataStoreId = pathParts[1];
630
655
  assert(this.contexts.has(dataStoreId), 0x2d7 /* No data store with specified id */);
656
+
657
+ /**
658
+ * When running GC in tombstone mode, datastore contexts are tombstoned. Tombstoned datastore contexts
659
+ * enable testing scenarios with accessing deleted content without actually deleting content from
660
+ * summaries.
661
+ */
662
+ if (tombstone) {
663
+ const dataStore = this.contexts.get(dataStoreId);
664
+ assert(dataStore !== undefined, 0x442 /* No data store retrieved with specified id */);
665
+ dataStore.setTombstone(true /* tombstone */);
666
+ continue;
667
+ }
668
+
631
669
  // Delete the contexts of unused data stores.
632
670
  this.contexts.delete(dataStoreId);
633
671
  // Delete the summarizer node of the unused data stores.