@fluidframework/container-runtime 2.0.0-internal.7.1.0 → 2.0.0-internal.7.1.1

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 (92) hide show
  1. package/api-report/container-runtime.api.md +2 -1
  2. package/dist/blobManager.d.ts +3 -6
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +17 -42
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/container-runtime-alpha.d.ts +4 -4
  7. package/dist/container-runtime-beta.d.ts +4 -4
  8. package/dist/container-runtime-public.d.ts +4 -4
  9. package/dist/container-runtime.d.ts +4 -4
  10. package/dist/containerRuntime.d.ts +5 -4
  11. package/dist/containerRuntime.d.ts.map +1 -1
  12. package/dist/containerRuntime.js +27 -6
  13. package/dist/containerRuntime.js.map +1 -1
  14. package/dist/dataStoreContext.d.ts +1 -0
  15. package/dist/dataStoreContext.d.ts.map +1 -1
  16. package/dist/dataStoreContext.js +39 -34
  17. package/dist/dataStoreContext.js.map +1 -1
  18. package/dist/dataStores.d.ts +0 -16
  19. package/dist/dataStores.d.ts.map +1 -1
  20. package/dist/dataStores.js +0 -48
  21. package/dist/dataStores.js.map +1 -1
  22. package/dist/gc/garbageCollection.d.ts +12 -3
  23. package/dist/gc/garbageCollection.d.ts.map +1 -1
  24. package/dist/gc/garbageCollection.js +41 -18
  25. package/dist/gc/garbageCollection.js.map +1 -1
  26. package/dist/gc/gcConfigs.d.ts +1 -0
  27. package/dist/gc/gcConfigs.d.ts.map +1 -1
  28. package/dist/gc/gcConfigs.js +12 -2
  29. package/dist/gc/gcConfigs.js.map +1 -1
  30. package/dist/gc/gcDefinitions.d.ts +21 -7
  31. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/gc/gcTelemetry.d.ts +12 -6
  34. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  35. package/dist/gc/gcTelemetry.js +74 -42
  36. package/dist/gc/gcTelemetry.js.map +1 -1
  37. package/dist/gc/index.d.ts +2 -2
  38. package/dist/gc/index.d.ts.map +1 -1
  39. package/dist/gc/index.js +1 -5
  40. package/dist/gc/index.js.map +1 -1
  41. package/dist/packageVersion.d.ts +1 -1
  42. package/dist/packageVersion.js +1 -1
  43. package/dist/packageVersion.js.map +1 -1
  44. package/lib/blobManager.d.ts +3 -6
  45. package/lib/blobManager.d.ts.map +1 -1
  46. package/lib/blobManager.js +18 -43
  47. package/lib/blobManager.js.map +1 -1
  48. package/lib/containerRuntime.d.ts +5 -4
  49. package/lib/containerRuntime.d.ts.map +1 -1
  50. package/lib/containerRuntime.js +28 -7
  51. package/lib/containerRuntime.js.map +1 -1
  52. package/lib/dataStoreContext.d.ts +1 -0
  53. package/lib/dataStoreContext.d.ts.map +1 -1
  54. package/lib/dataStoreContext.js +40 -35
  55. package/lib/dataStoreContext.js.map +1 -1
  56. package/lib/dataStores.d.ts +0 -16
  57. package/lib/dataStores.d.ts.map +1 -1
  58. package/lib/dataStores.js +2 -50
  59. package/lib/dataStores.js.map +1 -1
  60. package/lib/gc/garbageCollection.d.ts +12 -3
  61. package/lib/gc/garbageCollection.d.ts.map +1 -1
  62. package/lib/gc/garbageCollection.js +42 -19
  63. package/lib/gc/garbageCollection.js.map +1 -1
  64. package/lib/gc/gcConfigs.d.ts +1 -0
  65. package/lib/gc/gcConfigs.d.ts.map +1 -1
  66. package/lib/gc/gcConfigs.js +14 -4
  67. package/lib/gc/gcConfigs.js.map +1 -1
  68. package/lib/gc/gcDefinitions.d.ts +21 -7
  69. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  70. package/lib/gc/gcDefinitions.js.map +1 -1
  71. package/lib/gc/gcTelemetry.d.ts +12 -6
  72. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  73. package/lib/gc/gcTelemetry.js +74 -42
  74. package/lib/gc/gcTelemetry.js.map +1 -1
  75. package/lib/gc/index.d.ts +2 -2
  76. package/lib/gc/index.d.ts.map +1 -1
  77. package/lib/gc/index.js +2 -2
  78. package/lib/gc/index.js.map +1 -1
  79. package/lib/packageVersion.d.ts +1 -1
  80. package/lib/packageVersion.js +1 -1
  81. package/lib/packageVersion.js.map +1 -1
  82. package/package.json +21 -17
  83. package/src/blobManager.ts +18 -58
  84. package/src/containerRuntime.ts +39 -16
  85. package/src/dataStoreContext.ts +17 -8
  86. package/src/dataStores.ts +2 -80
  87. package/src/gc/garbageCollection.ts +53 -24
  88. package/src/gc/gcConfigs.ts +22 -4
  89. package/src/gc/gcDefinitions.ts +22 -7
  90. package/src/gc/gcTelemetry.ts +103 -56
  91. package/src/gc/index.ts +0 -4
  92. package/src/packageVersion.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.0.0-internal.7.1.0",
3
+ "version": "2.0.0-internal.7.1.1",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -35,18 +35,18 @@
35
35
  "temp-directory": "nyc/.nyc_output"
36
36
  },
37
37
  "dependencies": {
38
- "@fluid-internal/client-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
39
- "@fluidframework/container-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
40
- "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
41
- "@fluidframework/core-interfaces": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
42
- "@fluidframework/core-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
43
- "@fluidframework/datastore": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
44
- "@fluidframework/driver-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
45
- "@fluidframework/driver-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
38
+ "@fluid-internal/client-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
39
+ "@fluidframework/container-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
40
+ "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
41
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
42
+ "@fluidframework/core-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
43
+ "@fluidframework/datastore": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
44
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
45
+ "@fluidframework/driver-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
46
46
  "@fluidframework/protocol-definitions": "^3.0.0",
47
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
48
- "@fluidframework/runtime-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
49
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
47
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
48
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
49
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
50
50
  "double-ended-queue": "^2.1.0-0",
51
51
  "events": "^3.1.0",
52
52
  "lz4js": "^0.2.0",
@@ -54,15 +54,15 @@
54
54
  "uuid": "^9.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
57
+ "@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
58
58
  "@fluid-tools/benchmark": "^0.48.0",
59
59
  "@fluid-tools/build-cli": "^0.25.0",
60
60
  "@fluidframework/build-common": "^2.0.1",
61
61
  "@fluidframework/build-tools": "^0.25.0",
62
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.0.0",
62
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.1.0",
63
63
  "@fluidframework/eslint-config-fluid": "^3.0.0",
64
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
65
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
64
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
65
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
66
66
  "@microsoft/api-extractor": "^7.37.0",
67
67
  "@types/double-ended-queue": "^2.1.0",
68
68
  "@types/events": "^3.0.0",
@@ -83,7 +83,11 @@
83
83
  "typescript": "~5.1.6"
84
84
  },
85
85
  "typeValidation": {
86
- "broken": {}
86
+ "broken": {
87
+ "ClassDeclaration_ContainerRuntime": {
88
+ "forwardCompat": false
89
+ }
90
+ }
87
91
  },
88
92
  "scripts": {
89
93
  "build": "fluid-build . --task build",
@@ -37,14 +37,8 @@ import {
37
37
  ITelemetryContext,
38
38
  } from "@fluidframework/runtime-definitions";
39
39
 
40
- import { ContainerRuntime, TombstoneResponseHeaderKey } from "./containerRuntime";
41
- import {
42
- sendGCUnexpectedUsageEvent,
43
- disableAttachmentBlobSweepKey,
44
- throwOnTombstoneLoadKey,
45
- } from "./gc";
40
+ import { disableAttachmentBlobSweepKey } from "./gc";
46
41
  import { Throttler, formExponentialFn, IThrottler } from "./throttler";
47
- import { summarizerClientType } from "./summary";
48
42
  import { IBlobMetadata } from "./metadata";
49
43
 
50
44
  /**
@@ -119,7 +113,6 @@ export type IBlobManagerRuntime = Pick<
119
113
  IContainerRuntime,
120
114
  "attachState" | "connected" | "logger" | "clientDetails"
121
115
  > &
122
- Pick<ContainerRuntime, "gcTombstoneEnforcementAllowed"> &
123
116
  TypedEventEmitter<IContainerRuntimeEvents>;
124
117
 
125
118
  type ICreateBlobResponseWithTTL = ICreateBlobResponse & Partial<Record<"minTTLInSeconds", number>>;
@@ -189,8 +182,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
189
182
  ),
190
183
  );
191
184
 
192
- /** If true, throw an error when a tombstone attachment blob is retrieved. */
193
- private readonly throwOnTombstoneLoad: boolean;
194
185
  /**
195
186
  * This stores IDs of tombstoned blobs.
196
187
  * Tombstone is a temporary feature that imitates a blob getting swept by garbage collection.
@@ -229,11 +220,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
229
220
  logger: this.runtime.logger,
230
221
  namespace: "BlobManager",
231
222
  });
232
- // Read the feature flag that tells whether to throw when a tombstone blob is requested.
233
- this.throwOnTombstoneLoad =
234
- this.mc.config.getBoolean(throwOnTombstoneLoadKey) === true &&
235
- this.runtime.gcTombstoneEnforcementAllowed &&
236
- this.runtime.clientDetails.type !== summarizerClientType;
237
223
 
238
224
  this.redirectTable = this.load(snapshot);
239
225
 
@@ -376,14 +362,19 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
376
362
  }
377
363
 
378
364
  public async getBlob(blobId: string): Promise<ArrayBufferLike> {
379
- // Verify that the blob is valid, i.e., it has not been garbage collected. If it is, this will throw an error,
380
- // failing the call.
381
- this.verifyBlobValidity(blobId);
365
+ // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
366
+ // an error, failing the call.
367
+ this.verifyBlobNotDeleted(blobId);
368
+ // Let runtime know that the corresponding GC node was requested.
369
+ // Note that this will throw if the blob is inactive or tombstoned and throwing on incorrect usage
370
+ // is configured.
371
+ this.blobRequested(getGCNodePathFromBlobId(blobId));
382
372
 
383
373
  const pending = this.pendingBlobs.get(blobId);
384
374
  if (pending) {
385
375
  return pending.blob;
386
376
  }
377
+
387
378
  let storageId: string;
388
379
  if (this.runtime.attachState === AttachState.Detached) {
389
380
  assert(this.redirectTable.has(blobId), 0x383 /* requesting unknown blobs */);
@@ -397,9 +388,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
397
388
  storageId = attachedStorageId;
398
389
  }
399
390
 
400
- // Let runtime know that the corresponding GC node was requested.
401
- this.blobRequested(getGCNodePathFromBlobId(blobId));
402
-
403
391
  return PerformanceEvent.timedExecAsync(
404
392
  this.mc.logger,
405
393
  { eventName: "AttachmentReadBlob", id: storageId },
@@ -858,56 +846,28 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
858
846
  }
859
847
 
860
848
  /**
861
- * Verifies that the blob with given id is valid, i.e., it has not been garbage collected. If the blob is GC'd,
849
+ * Verifies that the blob with given id is not deleted, i.e., it has not been garbage collected. If the blob is GC'd,
862
850
  * log an error and throw if necessary.
863
851
  */
864
- private verifyBlobValidity(blobId: string) {
865
- /**
866
- * A blob can be in one of the following states:
867
- * 1. "deleted" - It has been deleted by garbage collection sweep phase.
868
- * 2. "tombstoned" - It is ready for deletion but sweep phase isn't enabled and tombstone feature is enabled.
869
- * 3. "valid" - It has not been deleted or tombstoned.
870
- */
871
- let state: "valid" | "tombstoned" | "deleted" = "valid";
872
- if (this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
873
- state = "deleted";
874
- } else if (this.tombstonedBlobs.has(blobId)) {
875
- state = "tombstoned";
876
- }
877
-
878
- if (state === "valid") {
852
+ private verifyBlobNotDeleted(blobId: string) {
853
+ if (!this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
879
854
  return;
880
855
  }
881
856
 
882
- // If the blob is deleted or throw on tombstone load is enabled, throw an error which will fail any attempt
883
- // to load the blob.
884
- const shouldFail = state === "deleted" || this.throwOnTombstoneLoad;
885
857
  const request = { url: blobId };
886
858
  const error = responseToException(
887
- createResponseError(
888
- 404,
889
- "Blob was deleted",
890
- request,
891
- state === "tombstoned" ? { [TombstoneResponseHeaderKey]: true } : undefined,
892
- ),
859
+ createResponseError(404, `Blob was deleted`, request),
893
860
  request,
894
861
  );
895
- sendGCUnexpectedUsageEvent(
896
- this.mc,
862
+ // Only log deleted events. Tombstone events are logged by garbage collector.
863
+ this.mc.logger.sendErrorEvent(
897
864
  {
898
- eventName:
899
- state === "tombstoned"
900
- ? "GC_Tombstone_Blob_Requested"
901
- : "GC_Deleted_Blob_Requested",
902
- category: shouldFail ? "error" : "generic",
903
- gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
865
+ eventName: "GC_Deleted_Blob_Requested",
866
+ pkg: BlobManager.basePath,
904
867
  },
905
- [BlobManager.basePath],
906
868
  error,
907
869
  );
908
- if (shouldFail) {
909
- throw error;
910
- }
870
+ throw error;
911
871
  }
912
872
 
913
873
  public setRedirectTable(table: Map<string, string>) {
@@ -166,7 +166,6 @@ import {
166
166
  IGarbageCollector,
167
167
  IGCRuntimeOptions,
168
168
  IGCStats,
169
- shouldAllowGcTombstoneEnforcement,
170
169
  trimLeadingAndTrailingSlashes,
171
170
  } from "./gc";
172
171
  import { channelToDataStore, IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
@@ -478,6 +477,7 @@ export interface RuntimeHeaderData {
478
477
  wait?: boolean;
479
478
  viaHandle?: boolean;
480
479
  allowTombstone?: boolean;
480
+ allowInactive?: boolean;
481
481
  }
482
482
 
483
483
  /** Default values for Runtime Headers */
@@ -485,6 +485,7 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
485
485
  wait: true,
486
486
  viaHandle: false,
487
487
  allowTombstone: false,
488
+ allowInactive: false,
488
489
  };
489
490
 
490
491
  /**
@@ -1140,10 +1141,15 @@ export class ContainerRuntime
1140
1141
  */
1141
1142
  private nextSummaryNumber: number;
1142
1143
 
1143
- /**
1144
- * If false, loading or using a Tombstoned object should merely log, not fail
1145
- */
1146
- public readonly gcTombstoneEnforcementAllowed: boolean;
1144
+ /** If false, loading or using a Tombstoned object should merely log, not fail */
1145
+ public get gcTombstoneEnforcementAllowed(): boolean {
1146
+ return this.garbageCollector.tombstoneEnforcementAllowed;
1147
+ }
1148
+
1149
+ /** If true, throw an error when a tombstone data store is used. */
1150
+ public get gcThrowOnTombstoneUsage(): boolean {
1151
+ return this.garbageCollector.throwOnTombstoneUsage;
1152
+ }
1147
1153
 
1148
1154
  /**
1149
1155
  * GUID to identify a document in telemetry
@@ -1290,11 +1296,6 @@ export class ContainerRuntime
1290
1296
  // Later updates come through calls to setConnectionState.
1291
1297
  this._connected = connected;
1292
1298
 
1293
- this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
1294
- metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
1295
- this.runtimeOptions.gcOptions[gcTombstoneGenerationOptionName] /* current */,
1296
- );
1297
-
1298
1299
  this.mc.logger.sendTelemetryEvent({
1299
1300
  eventName: "GCFeatureMatrix",
1300
1301
  metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
@@ -1778,7 +1779,10 @@ export class ContainerRuntime
1778
1779
  }
1779
1780
  : create404Response(request);
1780
1781
  } else if (requestParser.pathParts.length > 0) {
1781
- const dataStore = await this.getDataStoreFromRequest(id, request);
1782
+ // Differentiate between requesting the dataStore directly, or one of its children
1783
+ const requestForChild = !requestParser.isLeaf(1);
1784
+ const dataStore = await this.getDataStoreFromRequest(id, request, requestForChild);
1785
+
1782
1786
  const subRequest = requestParser.createSubRequest(1);
1783
1787
  // We always expect createSubRequest to include a leading slash, but asserting here to protect against
1784
1788
  // unintentionally modifying the url if that changes.
@@ -1811,6 +1815,7 @@ export class ContainerRuntime
1811
1815
  private async getDataStoreFromRequest(
1812
1816
  id: string,
1813
1817
  request: IRequest,
1818
+ requestForChild: boolean,
1814
1819
  ): Promise<IFluidDataStoreChannel> {
1815
1820
  const headerData: RuntimeHeaderData = {};
1816
1821
  if (typeof request.headers?.[RuntimeHeaders.wait] === "boolean") {
@@ -1822,24 +1827,36 @@ export class ContainerRuntime
1822
1827
  if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
1823
1828
  headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
1824
1829
  }
1830
+ if (typeof request.headers?.[AllowInactiveRequestHeaderKey] === "boolean") {
1831
+ headerData.allowInactive = request.headers[AllowInactiveRequestHeaderKey];
1832
+ }
1833
+
1834
+ // We allow Tombstone requests for sub-DataStore objects
1835
+ if (requestForChild) {
1836
+ headerData.allowTombstone = true;
1837
+ }
1825
1838
 
1826
1839
  await this.dataStores.waitIfPendingAlias(id);
1827
1840
  const internalId = this.internalId(id);
1828
1841
  const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
1829
- const dataStoreChannel = await dataStoreContext.realize();
1830
1842
 
1831
1843
  // Remove query params, leading and trailing slashes from the url. This is done to make sure the format is
1832
1844
  // the same as GC nodes id.
1833
1845
  const urlWithoutQuery = trimLeadingAndTrailingSlashes(request.url.split("?")[0]);
1846
+ // Get the initial snapshot details which contain the data store package path.
1847
+ const details = await dataStoreContext.getInitialSnapshotDetails();
1848
+
1849
+ // Note that this will throw if the data store is inactive or tombstoned and throwing on incorrect usage
1850
+ // is configured.
1834
1851
  this.garbageCollector.nodeUpdated(
1835
1852
  `/${urlWithoutQuery}`,
1836
1853
  "Loaded",
1837
1854
  undefined /* timestampMs */,
1838
- dataStoreContext.packagePath,
1839
- request?.headers,
1855
+ details.pkg,
1856
+ request,
1857
+ headerData,
1840
1858
  );
1841
-
1842
- return dataStoreChannel;
1859
+ return dataStoreContext.realize();
1843
1860
  }
1844
1861
 
1845
1862
  /** Adds the container's metadata to the given summary tree. */
@@ -2483,6 +2500,12 @@ export class ContainerRuntime
2483
2500
  "entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
2484
2501
  );
2485
2502
  }
2503
+ this.garbageCollector.nodeUpdated(
2504
+ `/${internalId}`,
2505
+ "Loaded",
2506
+ undefined /* timestampMs */,
2507
+ context.packagePath,
2508
+ );
2486
2509
  return channel.entryPoint;
2487
2510
  }
2488
2511
 
@@ -78,7 +78,7 @@ import {
78
78
  summarizerClientType,
79
79
  } from "./summary";
80
80
  import { ContainerRuntime } from "./containerRuntime";
81
- import { sendGCUnexpectedUsageEvent, throwOnTombstoneUsageKey } from "./gc";
81
+ import { sendGCUnexpectedUsageEvent } from "./gc";
82
82
 
83
83
  function createAttributes(
84
84
  pkg: readonly string[],
@@ -325,11 +325,7 @@ export abstract class FluidDataStoreContext
325
325
  this.mc.logger,
326
326
  );
327
327
 
328
- // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
329
- this.throwOnTombstoneUsage =
330
- this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
331
- this._containerRuntime.gcTombstoneEnforcementAllowed &&
332
- this.clientDetails.type !== summarizerClientType;
328
+ this.throwOnTombstoneUsage = this._containerRuntime.gcThrowOnTombstoneUsage;
333
329
 
334
330
  // By default, a data store can log maximum 10 local changes telemetry in summarizer.
335
331
  this.localChangesTelemetryCount =
@@ -486,7 +482,16 @@ export abstract class FluidDataStoreContext
486
482
  local: boolean,
487
483
  localOpMetadata: unknown,
488
484
  ): void {
489
- this.verifyNotClosed("process", true, extractSafePropertiesFromMessage(messageArg));
485
+ const safeTelemetryProps = extractSafePropertiesFromMessage(messageArg);
486
+ // On op process, tombstone error is logged in garbage collector. So, set "checkTombstone" to false when calling
487
+ // "verifyNotClosed" which logs tombstone errors. Throw error if tombstoned and throwing on load is configured.
488
+ this.verifyNotClosed("process", false /* checkTombstone */, safeTelemetryProps);
489
+ if (this.tombstoned && this.throwOnTombstoneUsage) {
490
+ throw new DataCorruptionError(
491
+ "Context is tombstoned! Call site [process]",
492
+ safeTelemetryProps,
493
+ );
494
+ }
490
495
 
491
496
  const innerContents = messageArg.contents as FluidDataStoreMessage;
492
497
  const message = {
@@ -1089,7 +1094,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
1089
1094
  return message;
1090
1095
  }
1091
1096
 
1092
- public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1097
+ private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
1093
1098
  let snapshot = this.snapshotTree;
1094
1099
  let attributes: ReadFluidDataStoreAttributes;
1095
1100
  let isRootDataStore = false;
@@ -1122,6 +1127,10 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
1122
1127
  isRootDataStore,
1123
1128
  snapshot,
1124
1129
  };
1130
+ });
1131
+
1132
+ public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1133
+ return this.initialSnapshotDetailsP;
1125
1134
  }
1126
1135
 
1127
1136
  /**
package/src/dataStores.ts CHANGED
@@ -50,12 +50,7 @@ import { buildSnapshotTree } from "@fluidframework/driver-utils";
50
50
  import { assert, Lazy } from "@fluidframework/core-utils";
51
51
  import { v4 as uuid } from "uuid";
52
52
  import { DataStoreContexts } from "./dataStoreContexts";
53
- import {
54
- ContainerRuntime,
55
- defaultRuntimeHeaderData,
56
- RuntimeHeaderData,
57
- TombstoneResponseHeaderKey,
58
- } from "./containerRuntime";
53
+ import { ContainerRuntime, defaultRuntimeHeaderData, RuntimeHeaderData } from "./containerRuntime";
59
54
  import {
60
55
  FluidDataStoreContext,
61
56
  RemoteFluidDataStoreContext,
@@ -65,12 +60,7 @@ import {
65
60
  } from "./dataStoreContext";
66
61
  import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs";
67
62
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
68
- import {
69
- GCNodeType,
70
- disableDatastoreSweepKey,
71
- throwOnTombstoneLoadKey,
72
- sendGCUnexpectedUsageEvent,
73
- } from "./gc";
63
+ import { GCNodeType, disableDatastoreSweepKey, sendGCUnexpectedUsageEvent } from "./gc";
74
64
  import {
75
65
  summarizerClientType,
76
66
  IContainerRuntimeMetadata,
@@ -104,8 +94,6 @@ export class DataStores implements IDisposable {
104
94
  // Stores the ids of new data stores between two GC runs. This is used to notify the garbage collector of new
105
95
  // root data stores that are added.
106
96
  private dataStoresSinceLastGC: string[] = [];
107
- /** If true, throw an error when a tombstone data store is retrieved. */
108
- private readonly throwOnTombstoneLoad: boolean;
109
97
  // The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
110
98
  // the container runtime to other nodes.
111
99
  private readonly containerRuntimeHandle: IFluidHandle;
@@ -140,12 +128,6 @@ export class DataStores implements IDisposable {
140
128
  this.runtime.IFluidHandleContext,
141
129
  );
142
130
 
143
- // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
144
- this.throwOnTombstoneLoad =
145
- this.mc.config.getBoolean(throwOnTombstoneLoadKey) === true &&
146
- this.runtime.gcTombstoneEnforcementAllowed &&
147
- this.runtime.clientDetails.type !== summarizerClientType;
148
-
149
131
  // Extract stores stored inside the snapshot
150
132
  const fluidDataStores = new Map<string, ISnapshotTree>();
151
133
  if (baseSnapshot) {
@@ -455,9 +437,6 @@ export class DataStores implements IDisposable {
455
437
  const request: IRequest = { url: id };
456
438
  throw responseToException(create404Response(request), request);
457
439
  }
458
-
459
- this.validateNotTombstoned(context, requestHeaderData);
460
-
461
440
  return context;
462
441
  }
463
442
 
@@ -477,8 +456,6 @@ export class DataStores implements IDisposable {
477
456
  if (context === undefined) {
478
457
  return undefined;
479
458
  }
480
- // Check if the data store is tombstoned. If so, we want to log a telemetry event.
481
- this.checkIfTombstoned(context, requestHeaderData);
482
459
  return context;
483
460
  }
484
461
 
@@ -529,61 +506,6 @@ export class DataStores implements IDisposable {
529
506
  }
530
507
  }
531
508
 
532
- /**
533
- * Checks if the data store has not been marked as tombstone by GC or not.
534
- * @param context - the data store context in question
535
- * @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
536
- * @returns true if the data store is tombstoned. Otherwise, returns false.
537
- */
538
- private checkIfTombstoned(
539
- context: FluidDataStoreContext,
540
- requestHeaderData: RuntimeHeaderData,
541
- ) {
542
- if (!context.tombstoned) {
543
- return false;
544
- }
545
- const logErrorEvent = this.throwOnTombstoneLoad && !requestHeaderData.allowTombstone;
546
- sendGCUnexpectedUsageEvent(
547
- this.mc,
548
- {
549
- eventName: "GC_Tombstone_DataStore_Requested",
550
- category: logErrorEvent ? "error" : "generic",
551
- isSummarizerClient: this.runtime.clientDetails.type === summarizerClientType,
552
- id: context.id,
553
- headers: JSON.stringify(requestHeaderData),
554
- gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
555
- },
556
- context.isLoaded ? context.packagePath : undefined,
557
- );
558
- return true;
559
- }
560
-
561
- /**
562
- * Validates that the data store context requested has not been marked as tombstone by GC.
563
- * @param context - the data store context in question
564
- * @param request - the request information to log if the validation detects the data store has been tombstoned
565
- * @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
566
- */
567
- private validateNotTombstoned(
568
- context: FluidDataStoreContext,
569
- requestHeaderData: RuntimeHeaderData,
570
- ) {
571
- if (this.checkIfTombstoned(context, requestHeaderData)) {
572
- // The requested data store is removed by gc. Create a 404 gc response exception.
573
- const request: IRequest = { url: context.id };
574
- const error = responseToException(
575
- createResponseError(404, "DataStore was deleted", request, {
576
- [TombstoneResponseHeaderKey]: true,
577
- }),
578
- request,
579
- );
580
- // Throw an error if configured via options and via request headers.
581
- if (this.throwOnTombstoneLoad && !requestHeaderData.allowTombstone) {
582
- throw error;
583
- }
584
- }
585
- }
586
-
587
509
  public processSignal(fluidDataStoreId: string, message: IInboundSignalMessage, local: boolean) {
588
510
  this.validateNotDeleted(fluidDataStoreId);
589
511
  const context = this.contexts.get(fluidDataStoreId);
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { LazyPromise, Timer } from "@fluidframework/core-utils";
7
- import { IRequest, IRequestHeader } from "@fluidframework/core-interfaces";
7
+ import { IRequest } from "@fluidframework/core-interfaces";
8
8
  import {
9
9
  gcTreeKey,
10
10
  IGarbageCollectionData,
@@ -23,9 +23,9 @@ import {
23
23
  } from "@fluidframework/telemetry-utils";
24
24
 
25
25
  import {
26
- AllowInactiveRequestHeaderKey,
27
26
  InactiveResponseHeaderKey,
28
- RuntimeHeaders,
27
+ RuntimeHeaderData,
28
+ TombstoneResponseHeaderKey,
29
29
  } from "../containerRuntime";
30
30
  import { ClientSessionExpiredError } from "../error";
31
31
  import { IRefreshSummaryResult } from "../summary";
@@ -113,6 +113,19 @@ export class GarbageCollector implements IGarbageCollector {
113
113
  private readonly summaryStateTracker: GCSummaryStateTracker;
114
114
  private readonly telemetryTracker: GCTelemetryTracker;
115
115
 
116
+ /** If false, loading or using a Tombstoned object should merely log, not fail */
117
+ public get tombstoneEnforcementAllowed(): boolean {
118
+ return this.configs.tombstoneEnforcementAllowed;
119
+ }
120
+ /** If true, throw an error when a tombstone data store is retrieved */
121
+ public get throwOnTombstoneLoad(): boolean {
122
+ return this.configs.throwOnTombstoneLoad;
123
+ }
124
+ /** If true, throw an error when a tombstone data store is used */
125
+ public get throwOnTombstoneUsage(): boolean {
126
+ return this.configs.throwOnTombstoneUsage;
127
+ }
128
+
116
129
  /** For a given node path, returns the node's package path. */
117
130
  private readonly getNodePackagePath: (
118
131
  nodePath: string,
@@ -176,7 +189,6 @@ export class GarbageCollector implements IGarbageCollector {
176
189
  this.mc,
177
190
  this.configs,
178
191
  this.isSummarizerClient,
179
- this.runtime.gcTombstoneEnforcementAllowed,
180
192
  createParams.createContainerMetadata,
181
193
  (nodeId: string) => this.runtime.getNodeType(nodeId),
182
194
  (nodeId: string) => this.unreferencedNodesState.get(nodeId),
@@ -850,11 +862,13 @@ export class GarbageCollector implements IGarbageCollector {
850
862
  }
851
863
 
852
864
  /**
853
- * Called when a node with the given id is updated. If the node is inactive, log an error.
865
+ * Called when a node with the given id is updated. If the node is inactive or tombstoned, this will log an error
866
+ * or throw an error if failing on incorrect usage is configured.
854
867
  * @param nodePath - The path of the node that changed.
855
868
  * @param reason - Whether the node was loaded or changed.
856
869
  * @param timestampMs - The timestamp when the node changed.
857
870
  * @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
871
+ * @param request - The original request for loads to preserve it in telemetry.
858
872
  * @param requestHeaders - If the node was loaded via request path, the headers in the request.
859
873
  */
860
874
  public nodeUpdated(
@@ -862,12 +876,15 @@ export class GarbageCollector implements IGarbageCollector {
862
876
  reason: "Loaded" | "Changed",
863
877
  timestampMs?: number,
864
878
  packagePath?: readonly string[],
865
- requestHeaders?: IRequestHeader,
879
+ request?: IRequest,
880
+ headerData?: RuntimeHeaderData,
866
881
  ) {
867
882
  if (!this.configs.shouldRunGC) {
868
883
  return;
869
884
  }
870
885
 
886
+ const isTombstoned = this.tombstones.includes(nodePath);
887
+
871
888
  // This will log if appropriate
872
889
  this.telemetryTracker.nodeUsed({
873
890
  id: nodePath,
@@ -876,32 +893,44 @@ export class GarbageCollector implements IGarbageCollector {
876
893
  timestampMs ?? this.runtime.getCurrentReferenceTimestampMs(),
877
894
  packagePath,
878
895
  completedGCRuns: this.completedRuns,
879
- isTombstoned: this.tombstones.includes(nodePath),
896
+ isTombstoned,
880
897
  lastSummaryTime: this.getLastSummaryTimestampMs(),
881
- viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
898
+ headers: headerData,
882
899
  });
883
900
 
884
- // Unless this is a Loaded event, we're done after telemetry tracking
885
- if (reason !== "Loaded") {
901
+ const nodeType = this.runtime.getNodeType(nodePath);
902
+
903
+ // Unless this is a Loaded event for a Blob or DataStore, we're done after telemetry tracking
904
+ if (reason !== "Loaded" || ![GCNodeType.Blob, GCNodeType.DataStore].includes(nodeType)) {
886
905
  return;
887
906
  }
888
907
 
889
- // We may throw when loading an Inactive object, depending on these preconditions
890
- const shouldThrowOnInactiveLoad =
891
- !this.isSummarizerClient &&
892
- this.configs.throwOnInactiveLoad === true &&
893
- requestHeaders?.[AllowInactiveRequestHeaderKey] !== true;
894
- const state = this.unreferencedNodesState.get(nodePath)?.state;
895
-
896
- if (shouldThrowOnInactiveLoad && state === "Inactive") {
897
- const request: IRequest = { url: nodePath };
898
- const error = responseToException(
899
- createResponseError(404, "Object is inactive", request, {
900
- [InactiveResponseHeaderKey]: true,
908
+ const errorRequest: IRequest = request ?? { url: nodePath };
909
+ // If the object is tombstoned and tombstone enforcement is configured, throw an error.
910
+ if (isTombstoned && this.throwOnTombstoneLoad && headerData?.allowTombstone !== true) {
911
+ // The requested data store is removed by gc. Create a 404 gc response exception.
912
+ throw responseToException(
913
+ createResponseError(404, `${nodeType} was tombstoned`, errorRequest, {
914
+ [TombstoneResponseHeaderKey]: true,
901
915
  }),
902
- request,
916
+ errorRequest,
903
917
  );
904
- throw error;
918
+ }
919
+
920
+ // If the object is inactive and inactive enforcement is configured, throw an error.
921
+ if (this.unreferencedNodesState.get(nodePath)?.state === "Inactive") {
922
+ const shouldThrowOnInactiveLoad =
923
+ !this.isSummarizerClient &&
924
+ this.configs.throwOnInactiveLoad === true &&
925
+ headerData?.allowInactive !== true;
926
+ if (shouldThrowOnInactiveLoad) {
927
+ throw responseToException(
928
+ createResponseError(404, `${nodeType} is inactive`, errorRequest, {
929
+ [InactiveResponseHeaderKey]: true,
930
+ }),
931
+ errorRequest,
932
+ );
933
+ }
905
934
  }
906
935
  }
907
936