@fluidframework/container-runtime 2.0.0-internal.7.0.0 → 2.0.0-internal.7.0.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 (87) hide show
  1. package/dist/blobManager.d.ts +3 -6
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +17 -42
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +5 -4
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +27 -6
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +1 -0
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +39 -34
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/dataStores.d.ts +0 -16
  14. package/dist/dataStores.d.ts.map +1 -1
  15. package/dist/dataStores.js +0 -48
  16. package/dist/dataStores.js.map +1 -1
  17. package/dist/gc/garbageCollection.d.ts +12 -3
  18. package/dist/gc/garbageCollection.d.ts.map +1 -1
  19. package/dist/gc/garbageCollection.js +41 -18
  20. package/dist/gc/garbageCollection.js.map +1 -1
  21. package/dist/gc/gcConfigs.d.ts +1 -0
  22. package/dist/gc/gcConfigs.d.ts.map +1 -1
  23. package/dist/gc/gcConfigs.js +12 -2
  24. package/dist/gc/gcConfigs.js.map +1 -1
  25. package/dist/gc/gcDefinitions.d.ts +21 -7
  26. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  27. package/dist/gc/gcDefinitions.js.map +1 -1
  28. package/dist/gc/gcTelemetry.d.ts +12 -6
  29. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  30. package/dist/gc/gcTelemetry.js +74 -42
  31. package/dist/gc/gcTelemetry.js.map +1 -1
  32. package/dist/gc/index.d.ts +2 -2
  33. package/dist/gc/index.d.ts.map +1 -1
  34. package/dist/gc/index.js +1 -5
  35. package/dist/gc/index.js.map +1 -1
  36. package/dist/packageVersion.d.ts +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/lib/blobManager.d.ts +3 -6
  40. package/lib/blobManager.d.ts.map +1 -1
  41. package/lib/blobManager.js +18 -43
  42. package/lib/blobManager.js.map +1 -1
  43. package/lib/containerRuntime.d.ts +5 -4
  44. package/lib/containerRuntime.d.ts.map +1 -1
  45. package/lib/containerRuntime.js +28 -7
  46. package/lib/containerRuntime.js.map +1 -1
  47. package/lib/dataStoreContext.d.ts +1 -0
  48. package/lib/dataStoreContext.d.ts.map +1 -1
  49. package/lib/dataStoreContext.js +40 -35
  50. package/lib/dataStoreContext.js.map +1 -1
  51. package/lib/dataStores.d.ts +0 -16
  52. package/lib/dataStores.d.ts.map +1 -1
  53. package/lib/dataStores.js +2 -50
  54. package/lib/dataStores.js.map +1 -1
  55. package/lib/gc/garbageCollection.d.ts +12 -3
  56. package/lib/gc/garbageCollection.d.ts.map +1 -1
  57. package/lib/gc/garbageCollection.js +42 -19
  58. package/lib/gc/garbageCollection.js.map +1 -1
  59. package/lib/gc/gcConfigs.d.ts +1 -0
  60. package/lib/gc/gcConfigs.d.ts.map +1 -1
  61. package/lib/gc/gcConfigs.js +14 -4
  62. package/lib/gc/gcConfigs.js.map +1 -1
  63. package/lib/gc/gcDefinitions.d.ts +21 -7
  64. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  65. package/lib/gc/gcDefinitions.js.map +1 -1
  66. package/lib/gc/gcTelemetry.d.ts +12 -6
  67. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  68. package/lib/gc/gcTelemetry.js +74 -42
  69. package/lib/gc/gcTelemetry.js.map +1 -1
  70. package/lib/gc/index.d.ts +2 -2
  71. package/lib/gc/index.d.ts.map +1 -1
  72. package/lib/gc/index.js +2 -2
  73. package/lib/gc/index.js.map +1 -1
  74. package/lib/packageVersion.d.ts +1 -1
  75. package/lib/packageVersion.js +1 -1
  76. package/lib/packageVersion.js.map +1 -1
  77. package/package.json +15 -18
  78. package/src/blobManager.ts +18 -58
  79. package/src/containerRuntime.ts +39 -16
  80. package/src/dataStoreContext.ts +17 -8
  81. package/src/dataStores.ts +2 -80
  82. package/src/gc/garbageCollection.ts +53 -24
  83. package/src/gc/gcConfigs.ts +22 -4
  84. package/src/gc/gcDefinitions.ts +22 -7
  85. package/src/gc/gcTelemetry.ts +103 -56
  86. package/src/gc/index.ts +0 -4
  87. 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.0.0",
3
+ "version": "2.0.0-internal.7.0.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.0.0 <2.0.0-internal.7.1.0",
39
- "@fluidframework/container-definitions": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
40
- "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
41
- "@fluidframework/core-interfaces": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
42
- "@fluidframework/core-utils": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
43
- "@fluidframework/datastore": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
44
- "@fluidframework/driver-definitions": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
45
- "@fluidframework/driver-utils": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
38
+ "@fluid-internal/client-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
39
+ "@fluidframework/container-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
40
+ "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
41
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
42
+ "@fluidframework/core-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
43
+ "@fluidframework/datastore": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
44
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
45
+ "@fluidframework/driver-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
46
46
  "@fluidframework/protocol-definitions": "^3.0.0",
47
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
48
- "@fluidframework/runtime-utils": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
49
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
47
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
48
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
49
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.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.0.0 <2.0.0-internal.7.1.0",
57
+ "@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
58
58
  "@fluid-tools/benchmark": "^0.48.0",
59
59
  "@fluid-tools/build-cli": "^0.24.0",
60
60
  "@fluidframework/build-common": "^2.0.0",
61
61
  "@fluidframework/build-tools": "^0.24.0",
62
62
  "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.6.3.0",
63
63
  "@fluidframework/eslint-config-fluid": "^2.1.0",
64
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
65
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.0.0 <2.0.0-internal.7.1.0",
64
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
65
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.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",
@@ -109,9 +109,6 @@
109
109
  "ClassDeclaration_SummaryCollection": {
110
110
  "backCompat": false
111
111
  },
112
- "InterfaceDeclaration_ISummarizerRuntime": {
113
- "backCompat": false
114
- },
115
112
  "InterfaceDeclaration_INackSummaryResult": {
116
113
  "backCompat": false
117
114
  },
@@ -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";
@@ -475,6 +474,7 @@ export interface RuntimeHeaderData {
475
474
  wait?: boolean;
476
475
  viaHandle?: boolean;
477
476
  allowTombstone?: boolean;
477
+ allowInactive?: boolean;
478
478
  }
479
479
 
480
480
  /** Default values for Runtime Headers */
@@ -482,6 +482,7 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
482
482
  wait: true,
483
483
  viaHandle: false,
484
484
  allowTombstone: false,
485
+ allowInactive: false,
485
486
  };
486
487
 
487
488
  /**
@@ -1090,10 +1091,15 @@ export class ContainerRuntime
1090
1091
  */
1091
1092
  private nextSummaryNumber: number;
1092
1093
 
1093
- /**
1094
- * If false, loading or using a Tombstoned object should merely log, not fail
1095
- */
1096
- public readonly gcTombstoneEnforcementAllowed: boolean;
1094
+ /** If false, loading or using a Tombstoned object should merely log, not fail */
1095
+ public get gcTombstoneEnforcementAllowed(): boolean {
1096
+ return this.garbageCollector.tombstoneEnforcementAllowed;
1097
+ }
1098
+
1099
+ /** If true, throw an error when a tombstone data store is used. */
1100
+ public get gcThrowOnTombstoneUsage(): boolean {
1101
+ return this.garbageCollector.throwOnTombstoneUsage;
1102
+ }
1097
1103
 
1098
1104
  /**
1099
1105
  * GUID to identify a document in telemetry
@@ -1240,11 +1246,6 @@ export class ContainerRuntime
1240
1246
  // Later updates come through calls to setConnectionState.
1241
1247
  this._connected = connected;
1242
1248
 
1243
- this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
1244
- metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
1245
- this.runtimeOptions.gcOptions[gcTombstoneGenerationOptionName] /* current */,
1246
- );
1247
-
1248
1249
  this.mc.logger.sendTelemetryEvent({
1249
1250
  eventName: "GCFeatureMatrix",
1250
1251
  metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
@@ -1726,7 +1727,10 @@ export class ContainerRuntime
1726
1727
  }
1727
1728
  : create404Response(request);
1728
1729
  } else if (requestParser.pathParts.length > 0) {
1729
- const dataStore = await this.getDataStoreFromRequest(id, request);
1730
+ // Differentiate between requesting the dataStore directly, or one of its children
1731
+ const requestForChild = !requestParser.isLeaf(1);
1732
+ const dataStore = await this.getDataStoreFromRequest(id, request, requestForChild);
1733
+
1730
1734
  const subRequest = requestParser.createSubRequest(1);
1731
1735
  // We always expect createSubRequest to include a leading slash, but asserting here to protect against
1732
1736
  // unintentionally modifying the url if that changes.
@@ -1758,6 +1762,7 @@ export class ContainerRuntime
1758
1762
  private async getDataStoreFromRequest(
1759
1763
  id: string,
1760
1764
  request: IRequest,
1765
+ requestForChild: boolean,
1761
1766
  ): Promise<IFluidDataStoreChannel> {
1762
1767
  const headerData: RuntimeHeaderData = {};
1763
1768
  if (typeof request.headers?.[RuntimeHeaders.wait] === "boolean") {
@@ -1769,24 +1774,36 @@ export class ContainerRuntime
1769
1774
  if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
1770
1775
  headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
1771
1776
  }
1777
+ if (typeof request.headers?.[AllowInactiveRequestHeaderKey] === "boolean") {
1778
+ headerData.allowInactive = request.headers[AllowInactiveRequestHeaderKey];
1779
+ }
1780
+
1781
+ // We allow Tombstone requests for sub-DataStore objects
1782
+ if (requestForChild) {
1783
+ headerData.allowTombstone = true;
1784
+ }
1772
1785
 
1773
1786
  await this.dataStores.waitIfPendingAlias(id);
1774
1787
  const internalId = this.internalId(id);
1775
1788
  const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
1776
- const dataStoreChannel = await dataStoreContext.realize();
1777
1789
 
1778
1790
  // Remove query params, leading and trailing slashes from the url. This is done to make sure the format is
1779
1791
  // the same as GC nodes id.
1780
1792
  const urlWithoutQuery = trimLeadingAndTrailingSlashes(request.url.split("?")[0]);
1793
+ // Get the initial snapshot details which contain the data store package path.
1794
+ const details = await dataStoreContext.getInitialSnapshotDetails();
1795
+
1796
+ // Note that this will throw if the data store is inactive or tombstoned and throwing on incorrect usage
1797
+ // is configured.
1781
1798
  this.garbageCollector.nodeUpdated(
1782
1799
  `/${urlWithoutQuery}`,
1783
1800
  "Loaded",
1784
1801
  undefined /* timestampMs */,
1785
- dataStoreContext.packagePath,
1786
- request?.headers,
1802
+ details.pkg,
1803
+ request,
1804
+ headerData,
1787
1805
  );
1788
-
1789
- return dataStoreChannel;
1806
+ return dataStoreContext.realize();
1790
1807
  }
1791
1808
 
1792
1809
  /** Adds the container's metadata to the given summary tree. */
@@ -2430,6 +2447,12 @@ export class ContainerRuntime
2430
2447
  "entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
2431
2448
  );
2432
2449
  }
2450
+ this.garbageCollector.nodeUpdated(
2451
+ `/${internalId}`,
2452
+ "Loaded",
2453
+ undefined /* timestampMs */,
2454
+ context.packagePath,
2455
+ );
2433
2456
  return channel.entryPoint;
2434
2457
  }
2435
2458
 
@@ -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