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

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 (112) 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 +33 -8
  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 +15 -2
  29. package/dist/gc/gcConfigs.js.map +1 -1
  30. package/dist/gc/gcDefinitions.d.ts +28 -9
  31. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  32. package/dist/gc/gcDefinitions.js +8 -3
  33. package/dist/gc/gcDefinitions.js.map +1 -1
  34. package/dist/gc/gcTelemetry.d.ts +12 -6
  35. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  36. package/dist/gc/gcTelemetry.js +91 -47
  37. package/dist/gc/gcTelemetry.js.map +1 -1
  38. package/dist/gc/index.d.ts +2 -2
  39. package/dist/gc/index.d.ts.map +1 -1
  40. package/dist/gc/index.js +3 -5
  41. package/dist/gc/index.js.map +1 -1
  42. package/dist/opLifecycle/opGroupingManager.d.ts +10 -2
  43. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  44. package/dist/opLifecycle/opGroupingManager.js +23 -3
  45. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  46. package/dist/opLifecycle/outbox.d.ts +0 -1
  47. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  48. package/dist/opLifecycle/outbox.js +2 -1
  49. package/dist/opLifecycle/outbox.js.map +1 -1
  50. package/dist/packageVersion.d.ts +1 -1
  51. package/dist/packageVersion.js +1 -1
  52. package/dist/packageVersion.js.map +1 -1
  53. package/lib/blobManager.d.ts +3 -6
  54. package/lib/blobManager.d.ts.map +1 -1
  55. package/lib/blobManager.js +18 -43
  56. package/lib/blobManager.js.map +1 -1
  57. package/lib/containerRuntime.d.ts +5 -4
  58. package/lib/containerRuntime.d.ts.map +1 -1
  59. package/lib/containerRuntime.js +34 -9
  60. package/lib/containerRuntime.js.map +1 -1
  61. package/lib/dataStoreContext.d.ts +1 -0
  62. package/lib/dataStoreContext.d.ts.map +1 -1
  63. package/lib/dataStoreContext.js +40 -35
  64. package/lib/dataStoreContext.js.map +1 -1
  65. package/lib/dataStores.d.ts +0 -16
  66. package/lib/dataStores.d.ts.map +1 -1
  67. package/lib/dataStores.js +2 -50
  68. package/lib/dataStores.js.map +1 -1
  69. package/lib/gc/garbageCollection.d.ts +12 -3
  70. package/lib/gc/garbageCollection.d.ts.map +1 -1
  71. package/lib/gc/garbageCollection.js +42 -19
  72. package/lib/gc/garbageCollection.js.map +1 -1
  73. package/lib/gc/gcConfigs.d.ts +1 -0
  74. package/lib/gc/gcConfigs.d.ts.map +1 -1
  75. package/lib/gc/gcConfigs.js +17 -4
  76. package/lib/gc/gcConfigs.js.map +1 -1
  77. package/lib/gc/gcDefinitions.d.ts +28 -9
  78. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  79. package/lib/gc/gcDefinitions.js +7 -2
  80. package/lib/gc/gcDefinitions.js.map +1 -1
  81. package/lib/gc/gcTelemetry.d.ts +12 -6
  82. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  83. package/lib/gc/gcTelemetry.js +92 -48
  84. package/lib/gc/gcTelemetry.js.map +1 -1
  85. package/lib/gc/index.d.ts +2 -2
  86. package/lib/gc/index.d.ts.map +1 -1
  87. package/lib/gc/index.js +2 -2
  88. package/lib/gc/index.js.map +1 -1
  89. package/lib/opLifecycle/opGroupingManager.d.ts +10 -2
  90. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  91. package/lib/opLifecycle/opGroupingManager.js +23 -3
  92. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  93. package/lib/opLifecycle/outbox.d.ts +0 -1
  94. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  95. package/lib/opLifecycle/outbox.js +2 -1
  96. package/lib/opLifecycle/outbox.js.map +1 -1
  97. package/lib/packageVersion.d.ts +1 -1
  98. package/lib/packageVersion.js +1 -1
  99. package/lib/packageVersion.js.map +1 -1
  100. package/package.json +21 -17
  101. package/src/blobManager.ts +18 -58
  102. package/src/containerRuntime.ts +50 -18
  103. package/src/dataStoreContext.ts +17 -8
  104. package/src/dataStores.ts +2 -80
  105. package/src/gc/garbageCollection.ts +53 -24
  106. package/src/gc/gcConfigs.ts +28 -4
  107. package/src/gc/gcDefinitions.ts +32 -9
  108. package/src/gc/gcTelemetry.ts +123 -65
  109. package/src/gc/index.ts +2 -4
  110. package/src/opLifecycle/opGroupingManager.ts +37 -2
  111. package/src/opLifecycle/outbox.ts +4 -2
  112. package/src/packageVersion.ts +1 -1
@@ -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
 
@@ -24,8 +24,11 @@ import {
24
24
  runSessionExpiryKey,
25
25
  runSweepKey,
26
26
  stableGCVersion,
27
+ throwOnTombstoneLoadOverrideKey,
28
+ throwOnTombstoneUsageKey,
29
+ gcThrowOnTombstoneLoadOptionName,
27
30
  } from "./gcDefinitions";
28
- import { getGCVersion, shouldAllowGcSweep } from "./gcHelpers";
31
+ import { getGCVersion, shouldAllowGcSweep, shouldAllowGcTombstoneEnforcement } from "./gcHelpers";
29
32
 
30
33
  /**
31
34
  * Generates configurations for the Garbage Collector that it uses to determine what to run and how.
@@ -42,6 +45,7 @@ export function generateGCConfigs(
42
45
  gcOptions: IGCRuntimeOptions;
43
46
  metadata: IContainerRuntimeMetadata | undefined;
44
47
  existing: boolean;
48
+ isSummarizerClient: boolean;
45
49
  },
46
50
  ): IGarbageCollectorConfigs {
47
51
  let gcEnabled: boolean;
@@ -152,8 +156,6 @@ export function generateGCConfigs(
152
156
  throw new UsageError("inactive timeout should not be greater than the sweep timeout");
153
157
  }
154
158
 
155
- const throwOnInactiveLoad: boolean | undefined = createParams.gcOptions.throwOnInactiveLoad;
156
-
157
159
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
158
160
  const testMode =
159
161
  mc.config.getBoolean(gcTestModeKey) ?? createParams.gcOptions.runGCInTestMode === true;
@@ -162,6 +164,25 @@ export function generateGCConfigs(
162
164
  const tombstoneMode = !shouldRunSweep && mc.config.getBoolean(disableTombstoneKey) !== true;
163
165
  const runFullGC = createParams.gcOptions.runFullGC;
164
166
 
167
+ const throwOnInactiveLoad: boolean | undefined = createParams.gcOptions.throwOnInactiveLoad;
168
+ const tombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
169
+ createParams.metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
170
+ createParams.gcOptions[gcTombstoneGenerationOptionName] /* current */,
171
+ );
172
+
173
+ const throwOnTombstoneLoadConfig =
174
+ mc.config.getBoolean(throwOnTombstoneLoadOverrideKey) ??
175
+ createParams.gcOptions[gcThrowOnTombstoneLoadOptionName] ??
176
+ false;
177
+ const throwOnTombstoneLoad =
178
+ throwOnTombstoneLoadConfig &&
179
+ tombstoneEnforcementAllowed &&
180
+ !createParams.isSummarizerClient;
181
+ const throwOnTombstoneUsage =
182
+ mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
183
+ tombstoneEnforcementAllowed &&
184
+ !createParams.isSummarizerClient;
185
+
165
186
  return {
166
187
  gcEnabled,
167
188
  sweepEnabled,
@@ -173,10 +194,13 @@ export function generateGCConfigs(
173
194
  sessionExpiryTimeoutMs,
174
195
  sweepTimeoutMs,
175
196
  inactiveTimeoutMs,
176
- throwOnInactiveLoad,
177
197
  persistedGcFeatureMatrix,
178
198
  gcVersionInBaseSnapshot,
179
199
  gcVersionInEffect,
200
+ throwOnInactiveLoad,
201
+ tombstoneEnforcementAllowed,
202
+ throwOnTombstoneLoad,
203
+ throwOnTombstoneUsage,
180
204
  };
181
205
  }
182
206
 
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
7
- import { IRequestHeader } from "@fluidframework/core-interfaces";
7
+ import { IRequest } from "@fluidframework/core-interfaces";
8
8
  import { ISnapshotTree } from "@fluidframework/protocol-definitions";
9
9
  import {
10
10
  IGarbageCollectionData,
@@ -19,6 +19,7 @@ import {
19
19
  ICreateContainerMetadata,
20
20
  IRefreshSummaryResult,
21
21
  } from "../summary";
22
+ import { RuntimeHeaderData } from "../containerRuntime";
22
23
 
23
24
  export type GCVersion = number;
24
25
 
@@ -34,6 +35,13 @@ export const nextGCVersion: GCVersion = 4;
34
35
  * Otherwise, only enforce GC Tombstone if the passed in value matches the persisted value
35
36
  */
36
37
  export const gcTombstoneGenerationOptionName = "gcTombstoneGeneration";
38
+
39
+ /**
40
+ * This undocumented GC Option (on ContainerRuntime Options) allows an app to enable throwing an error when tombstone
41
+ * object is loaded (requested).
42
+ */
43
+ export const gcThrowOnTombstoneLoadOptionName = "gcThrowOnTombstoneLoad";
44
+
37
45
  /**
38
46
  * This GC Option (on ContainerRuntime Options) allows an app to disable GC Sweep on old documents by incrementing this value.
39
47
  *
@@ -55,8 +63,9 @@ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
55
63
  export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
56
64
  /** Config key to disable the tombstone feature, i.e., tombstone information is not read / written into summary. */
57
65
  export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
58
- /** Config key to enable throwing an error when tombstone object is loaded (requested). */
59
- export const throwOnTombstoneLoadKey = "Fluid.GarbageCollection.ThrowOnTombstoneLoad";
66
+ /** Config key to override throwing an error when tombstone object is loaded (requested). */
67
+ export const throwOnTombstoneLoadOverrideKey =
68
+ "Fluid.GarbageCollection.ThrowOnTombstoneLoadOverride";
60
69
  /** Config key to enable throwing an error when tombstone object is used (e.g. outgoing or incoming ops). */
61
70
  export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
62
71
  /** Config key to enable GC version upgrade. */
@@ -193,8 +202,6 @@ export interface IGarbageCollectionRuntime {
193
202
  getNodeType(nodePath: string): GCNodeType;
194
203
  /** Called when the runtime should close because of an error. */
195
204
  closeFn: (error?: ICriticalContainerError) => void;
196
- /** If false, loading or using a Tombstoned object should merely log, not fail */
197
- gcTombstoneEnforcementAllowed: boolean;
198
205
  }
199
206
 
200
207
  /** Defines the contract for the garbage collector. */
@@ -205,6 +212,12 @@ export interface IGarbageCollector {
205
212
  readonly summaryStateNeedsReset: boolean;
206
213
  /** The count of data stores whose GC state updated since the last summary. */
207
214
  readonly updatedDSCountSinceLastSummary: number;
215
+ /** Tells whether tombstone feature is enabled and enforced. */
216
+ readonly tombstoneEnforcementAllowed: boolean;
217
+ /** Tells whether loading a tombstone object should fail or merely log. */
218
+ readonly throwOnTombstoneLoad: boolean;
219
+ /** Tells whether using a tombstone object should fail or merely log. */
220
+ readonly throwOnTombstoneUsage: boolean;
208
221
  /** Initialize the state from the base snapshot after its creation. */
209
222
  initializeBaseState(): Promise<void>;
210
223
  /** Run garbage collection and update the reference / used state of the system. */
@@ -228,13 +241,17 @@ export interface IGarbageCollector {
228
241
  getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
229
242
  /** Called when the latest summary of the system has been refreshed. */
230
243
  refreshLatestSummary(result: IRefreshSummaryResult): Promise<void>;
231
- /** Called when a node is updated. Used to detect and log when an inactive node is changed or loaded. */
244
+ /**
245
+ * Called when a node with the given path is updated. If the node is inactive or tombstoned, this will log an error
246
+ * or throw an error if failing on incorrect usage is configured.
247
+ */
232
248
  nodeUpdated(
233
249
  nodePath: string,
234
250
  reason: "Loaded" | "Changed",
235
251
  timestampMs?: number,
236
252
  packagePath?: readonly string[],
237
- requestHeaders?: IRequestHeader,
253
+ request?: IRequest,
254
+ headerData?: RuntimeHeaderData,
238
255
  ): void;
239
256
  /** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
240
257
  addedOutboundReference(fromNodePath: string, toNodePath: string): void;
@@ -332,8 +349,6 @@ export interface IGarbageCollectorConfigs {
332
349
  readonly sweepTimeoutMs: number | undefined;
333
350
  /** The time after which an unreferenced node is inactive. */
334
351
  readonly inactiveTimeoutMs: number;
335
- /** It is easier for users to diagnose InactiveObject usage if we throw on load, which this option enables */
336
- readonly throwOnInactiveLoad: boolean | undefined;
337
352
  /** Tracks whether GC should run in test mode. In this mode, unreferenced objects are deleted immediately. */
338
353
  readonly testMode: boolean;
339
354
  /**
@@ -349,6 +364,14 @@ export interface IGarbageCollectorConfigs {
349
364
  readonly gcVersionInBaseSnapshot: GCVersion | undefined;
350
365
  /** The current version of GC data in the running code */
351
366
  readonly gcVersionInEffect: GCVersion;
367
+ /** It is easier for users to diagnose InactiveObject usage if we throw on load, which this option enables */
368
+ readonly throwOnInactiveLoad: boolean | undefined;
369
+ /** If false, loading or using a Tombstoned object should merely log, not fail */
370
+ readonly tombstoneEnforcementAllowed: boolean;
371
+ /** If true, throw an error when a tombstone data store is retrieved */
372
+ readonly throwOnTombstoneLoad: boolean;
373
+ /** If true, throw an error when a tombstone data store is used. */
374
+ readonly throwOnTombstoneUsage: boolean;
352
375
  }
353
376
 
354
377
  /** The state of node that is unreferenced. */