@fluidframework/container-runtime 2.0.0-internal.8.0.7 → 2.0.0-internal.8.0.8

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 (58) hide show
  1. package/dist/containerRuntime.d.ts.map +1 -1
  2. package/dist/containerRuntime.js +8 -2
  3. package/dist/containerRuntime.js.map +1 -1
  4. package/dist/gc/garbageCollection.d.ts +12 -1
  5. package/dist/gc/garbageCollection.d.ts.map +1 -1
  6. package/dist/gc/garbageCollection.js +61 -7
  7. package/dist/gc/garbageCollection.js.map +1 -1
  8. package/dist/gc/gcDefinitions.d.ts +19 -3
  9. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  10. package/dist/gc/gcDefinitions.js +5 -1
  11. package/dist/gc/gcDefinitions.js.map +1 -1
  12. package/dist/gc/gcSummaryStateTracker.d.ts +8 -1
  13. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  14. package/dist/gc/gcSummaryStateTracker.js +13 -1
  15. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  16. package/dist/gc/gcTelemetry.d.ts +1 -0
  17. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  18. package/dist/gc/gcTelemetry.js.map +1 -1
  19. package/dist/gc/index.d.ts +1 -1
  20. package/dist/gc/index.d.ts.map +1 -1
  21. package/dist/gc/index.js +3 -1
  22. package/dist/gc/index.js.map +1 -1
  23. package/dist/packageVersion.d.ts +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/lib/containerRuntime.d.ts.map +1 -1
  27. package/lib/containerRuntime.js +8 -2
  28. package/lib/containerRuntime.js.map +1 -1
  29. package/lib/gc/garbageCollection.d.ts +12 -1
  30. package/lib/gc/garbageCollection.d.ts.map +1 -1
  31. package/lib/gc/garbageCollection.js +62 -8
  32. package/lib/gc/garbageCollection.js.map +1 -1
  33. package/lib/gc/gcDefinitions.d.ts +19 -3
  34. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  35. package/lib/gc/gcDefinitions.js +4 -0
  36. package/lib/gc/gcDefinitions.js.map +1 -1
  37. package/lib/gc/gcSummaryStateTracker.d.ts +8 -1
  38. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  39. package/lib/gc/gcSummaryStateTracker.js +13 -1
  40. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  41. package/lib/gc/gcTelemetry.d.ts +1 -0
  42. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  43. package/lib/gc/gcTelemetry.js.map +1 -1
  44. package/lib/gc/index.d.ts +1 -1
  45. package/lib/gc/index.d.ts.map +1 -1
  46. package/lib/gc/index.js +1 -1
  47. package/lib/gc/index.js.map +1 -1
  48. package/lib/packageVersion.d.ts +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/package.json +16 -16
  52. package/src/containerRuntime.ts +9 -2
  53. package/src/gc/garbageCollection.ts +69 -7
  54. package/src/gc/gcDefinitions.ts +20 -4
  55. package/src/gc/gcSummaryStateTracker.ts +14 -1
  56. package/src/gc/gcTelemetry.ts +1 -0
  57. package/src/gc/index.ts +3 -0
  58. 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.8.0.7",
3
+ "version": "2.0.0-internal.8.0.8",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -35,19 +35,19 @@
35
35
  "temp-directory": "nyc/.nyc_output"
36
36
  },
37
37
  "dependencies": {
38
- "@fluid-internal/client-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
39
- "@fluidframework/container-definitions": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
40
- "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
41
- "@fluidframework/core-interfaces": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
42
- "@fluidframework/core-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
43
- "@fluidframework/datastore": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
44
- "@fluidframework/driver-definitions": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
45
- "@fluidframework/driver-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
46
- "@fluidframework/id-compressor": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
38
+ "@fluid-internal/client-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
39
+ "@fluidframework/container-definitions": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
40
+ "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
41
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
42
+ "@fluidframework/core-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
43
+ "@fluidframework/datastore": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
44
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
45
+ "@fluidframework/driver-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
46
+ "@fluidframework/id-compressor": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
47
47
  "@fluidframework/protocol-definitions": "^3.0.0",
48
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
49
- "@fluidframework/runtime-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
50
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
48
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
49
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
50
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
51
51
  "double-ended-queue": "^2.1.0-0",
52
52
  "events": "^3.1.0",
53
53
  "lz4js": "^0.2.0",
@@ -56,15 +56,15 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@arethetypeswrong/cli": "^0.13.3",
59
- "@fluid-private/stochastic-test-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
59
+ "@fluid-private/stochastic-test-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
60
60
  "@fluid-tools/benchmark": "^0.48.0",
61
61
  "@fluid-tools/build-cli": "^0.28.0",
62
62
  "@fluidframework/build-common": "^2.0.3",
63
63
  "@fluidframework/build-tools": "^0.28.0",
64
64
  "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.2.0",
65
65
  "@fluidframework/eslint-config-fluid": "^3.1.0",
66
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
67
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.8.0.7 <2.0.0-internal.8.1.0",
66
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
67
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.8.0.8 <2.0.0-internal.8.1.0",
68
68
  "@microsoft/api-extractor": "^7.38.3",
69
69
  "@types/double-ended-queue": "^2.1.0",
70
70
  "@types/events": "^3.0.0",
@@ -2909,6 +2909,12 @@ export class ContainerRuntime
2909
2909
  * data store or an attachment blob.
2910
2910
  */
2911
2911
  public async getGCNodePackagePath(nodePath: string): Promise<readonly string[] | undefined> {
2912
+ // GC uses "/" when adding "root" references, e.g. for Aliasing or as part of Tombstone Auto-Recovery.
2913
+ // These have no package path so return a special value.
2914
+ if (nodePath === "/") {
2915
+ return ["_gcRoot"];
2916
+ }
2917
+
2912
2918
  switch (this.getNodeType(nodePath)) {
2913
2919
  case GCNodeType.Blob:
2914
2920
  return [BlobManager.basePath];
@@ -3691,6 +3697,7 @@ export class ContainerRuntime
3691
3697
  localOpMetadata: unknown,
3692
3698
  opMetadata: Record<string, unknown> | undefined,
3693
3699
  ) {
3700
+ assert(!this.isSummarizerClient, "Summarizer never reconnects so should never resubmit");
3694
3701
  switch (message.type) {
3695
3702
  case ContainerMessageType.FluidDataStoreOp:
3696
3703
  // For Operations, call resubmitDataStoreOp which will find the right store
@@ -3712,8 +3719,8 @@ export class ContainerRuntime
3712
3719
  this.submit(message);
3713
3720
  break;
3714
3721
  case ContainerMessageType.GC:
3715
- // GC op is only sent in summarizer which should never reconnect.
3716
- throw new LoggingError("GC op not expected to be resubmitted in summarizer");
3722
+ this.submit(message);
3723
+ break;
3717
3724
  default: {
3718
3725
  // This case should be very rare - it would imply an op was stashed from a
3719
3726
  // future version of runtime code and now is being applied on an older version
@@ -45,6 +45,7 @@ import {
45
45
  ISweepPhaseStats,
46
46
  GarbageCollectionMessage,
47
47
  GarbageCollectionMessageType,
48
+ disableAutoRecoveryKey,
48
49
  } from "./gcDefinitions";
49
50
  import {
50
51
  cloneGCData,
@@ -441,7 +442,9 @@ export class GarbageCollector implements IGarbageCollector {
441
442
  ): Promise<IGCStats | undefined> {
442
443
  const fullGC =
443
444
  options.fullGC ??
444
- (this.configs.runFullGC === true || this.summaryStateTracker.doesSummaryStateNeedReset);
445
+ (this.configs.runFullGC === true ||
446
+ this.summaryStateTracker.autoRecovery.fullGCRequested() ||
447
+ this.summaryStateTracker.doesSummaryStateNeedReset);
445
448
 
446
449
  // Add the options that are used to run GC to the telemetry context.
447
450
  telemetryContext?.setMultiple("fluid_GC", "Options", {
@@ -870,21 +873,37 @@ export class GarbageCollector implements IGarbageCollector {
870
873
  * @param local - Whether it was send by this client.
871
874
  */
872
875
  public processMessage(message: ContainerRuntimeGCMessage, local: boolean) {
873
- switch (message.contents.type) {
874
- case "Sweep": {
876
+ const gcMessageType = message.contents.type;
877
+ switch (gcMessageType) {
878
+ case GarbageCollectionMessageType.Sweep: {
875
879
  // Delete the nodes whose ids are present in the contents.
876
880
  this.deleteSweepReadyNodes(message.contents.deletedNodeIds);
877
881
  break;
878
882
  }
883
+ case GarbageCollectionMessageType.TombstoneLoaded: {
884
+ if (this.mc.config.getBoolean(disableAutoRecoveryKey) === true) {
885
+ break;
886
+ }
887
+
888
+ // Mark the node as referenced to ensure it isn't Swept
889
+ const tombstonedNodePath = message.contents.nodePath;
890
+ this.addedOutboundReference("/", tombstonedNodePath, true /* autorecovery */);
891
+
892
+ // In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
893
+ // do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
894
+ this.summaryStateTracker.autoRecovery.requestFullGCOnNextRun();
895
+
896
+ break;
897
+ }
879
898
  default: {
880
899
  if (
881
900
  !compatBehaviorAllowsGCMessageType(
882
- message.contents.type,
901
+ gcMessageType,
883
902
  message.compatDetails?.behavior,
884
903
  )
885
904
  ) {
886
905
  const error = DataProcessingError.create(
887
- `Garbage collection message of unknown type ${message.contents.type}`,
906
+ `Garbage collection message of unknown type ${gcMessageType}`,
888
907
  "processMessage",
889
908
  );
890
909
  throw error;
@@ -977,6 +996,15 @@ export class GarbageCollector implements IGarbageCollector {
977
996
  headers: headerData,
978
997
  });
979
998
 
999
+ // Any time we log a Tombstone Loaded error (via Telemetry Tracker),
1000
+ // we want to also trigger autorecovery to avoid the object being deleted
1001
+ // Note: We don't need to trigger on "Changed" because any change will cause the object
1002
+ // to be loaded by the Summarizer, and auto-recovery will be triggered then.
1003
+ if (isTombstoned && reason === "Loaded") {
1004
+ // Note that when a DataStore and its DDS are all loaded, each will trigger AutoRecovery for itself.
1005
+ this.triggerAutoRecovery(nodePath);
1006
+ }
1007
+
980
1008
  const nodeType = this.runtime.getNodeType(nodePath);
981
1009
 
982
1010
  // Unless this is a Loaded event for a Blob or DataStore, we're done after telemetry tracking
@@ -985,7 +1013,6 @@ export class GarbageCollector implements IGarbageCollector {
985
1013
  }
986
1014
 
987
1015
  const errorRequest: IRequest = request ?? { url: nodePath };
988
- // If the object is tombstoned and tombstone enforcement is configured, throw an error.
989
1016
  if (isTombstoned && this.throwOnTombstoneLoad && headerData?.allowTombstone !== true) {
990
1017
  // The requested data store is removed by gc. Create a 404 gc response exception.
991
1018
  throw responseToException(
@@ -1013,14 +1040,42 @@ export class GarbageCollector implements IGarbageCollector {
1013
1040
  }
1014
1041
  }
1015
1042
 
1043
+ /**
1044
+ * The given node should have its unreferenced state reset in the next GC,
1045
+ * even if the true GC graph shows it is unreferenced. This will
1046
+ * prevent it from being deleted by Sweep (after the Grace Period).
1047
+ *
1048
+ * Submit a GC op indicating that the Tombstone with the given path has been loaded.
1049
+ * Broadcasting this information in the op stream allows the Summarizer to reset unreferenced state
1050
+ * before runnint GC next.
1051
+ */
1052
+ private triggerAutoRecovery(nodePath: string) {
1053
+ if (this.mc.config.getBoolean(disableAutoRecoveryKey) === true) {
1054
+ return;
1055
+ }
1056
+
1057
+ // Use compat behavior "Ignore" since this is an optimization to opportunistically protect
1058
+ // objects from deletion, so it's fine for older clients to ignore this op.
1059
+ const containerGCMessage: ContainerRuntimeGCMessage = {
1060
+ type: ContainerMessageType.GC,
1061
+ contents: {
1062
+ type: GarbageCollectionMessageType.TombstoneLoaded,
1063
+ nodePath,
1064
+ },
1065
+ compatDetails: { behavior: "Ignore" },
1066
+ };
1067
+ this.submitMessage(containerGCMessage);
1068
+ }
1069
+
1016
1070
  /**
1017
1071
  * Called when an outbound reference is added to a node. This is used to identify all nodes that have been
1018
1072
  * referenced between summaries so that their unreferenced timestamp can be reset.
1019
1073
  *
1020
1074
  * @param fromNodePath - The node from which the reference is added.
1021
1075
  * @param toNodePath - The node to which the reference is added.
1076
+ * @param autorecovery - This reference is added artificially, for autorecovery. Used for logging.
1022
1077
  */
1023
- public addedOutboundReference(fromNodePath: string, toNodePath: string) {
1078
+ public addedOutboundReference(fromNodePath: string, toNodePath: string, autorecovery?: true) {
1024
1079
  if (!this.configs.shouldRunGC) {
1025
1080
  return;
1026
1081
  }
@@ -1038,7 +1093,14 @@ export class GarbageCollector implements IGarbageCollector {
1038
1093
  isTombstoned: this.tombstones.includes(toNodePath),
1039
1094
  lastSummaryTime: this.getLastSummaryTimestampMs(),
1040
1095
  fromId: fromNodePath,
1096
+ autorecovery,
1041
1097
  });
1098
+
1099
+ // This node is referenced - Clear its unreferenced state
1100
+ // But don't delete the node id from the map yet.
1101
+ // When generating GC stats, the set of nodes in here is used as the baseline for
1102
+ // what was unreferenced in the last GC run.
1103
+ this.unreferencedNodesState.get(toNodePath)?.stopTracking();
1042
1104
  }
1043
1105
 
1044
1106
  /**
@@ -78,6 +78,8 @@ export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombston
78
78
  export const gcVersionUpgradeToV4Key = "Fluid.GarbageCollection.GCVersionUpgradeToV4";
79
79
  /** Config key to disable GC sweep for datastores. They'll merely be Tombstoned. */
80
80
  export const disableDatastoreSweepKey = "Fluid.GarbageCollection.DisableDataStoreSweep";
81
+ /** Config key to disable auto-recovery mechanism that protects Tombstones that are loaded from being swept (use true) */
82
+ export const disableAutoRecoveryKey = "Fluid.GarbageCollection.DisableAutoRecovery";
81
83
 
82
84
  // One day in milliseconds.
83
85
  export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
@@ -233,6 +235,8 @@ export type GCNodeType = (typeof GCNodeType)[keyof typeof GCNodeType];
233
235
  export const GarbageCollectionMessageType = {
234
236
  /** Message sent directing GC to delete the given nodes */
235
237
  Sweep: "Sweep",
238
+ /** Message sent notifying GC that a Tombstoned object was Loaded */
239
+ TombstoneLoaded: "TombstoneLoaded",
236
240
  } as const;
237
241
 
238
242
  /**
@@ -246,16 +250,28 @@ export type GarbageCollectionMessageType =
246
250
  * @internal
247
251
  */
248
252
  export interface ISweepMessage {
249
- type: "Sweep";
250
- // The ids of nodes that are deleted.
253
+ /** @see GarbageCollectionMessageType.Sweep */
254
+ type: typeof GarbageCollectionMessageType.Sweep;
255
+ /** The ids of nodes that are deleted. */
251
256
  deletedNodeIds: string[];
252
257
  }
253
258
 
259
+ /**
260
+ * The GC TombstoneLoaded message.
261
+ * @internal
262
+ */
263
+ export interface ITombstoneLoadedMessage {
264
+ /** @see GarbageCollectionMessageType.TombstoneLoaded */
265
+ type: typeof GarbageCollectionMessageType.TombstoneLoaded;
266
+ /** The id of Tombstoned node that was loaded. */
267
+ nodePath: string;
268
+ }
269
+
254
270
  /**
255
271
  * Type for a message to be used for sending / received garbage collection messages.
256
272
  * @internal
257
273
  */
258
- export type GarbageCollectionMessage = ISweepMessage;
274
+ export type GarbageCollectionMessage = ISweepMessage | ITombstoneLoadedMessage;
259
275
 
260
276
  /**
261
277
  * Defines the APIs for the runtime object to be passed to the garbage collector.
@@ -335,7 +351,7 @@ export interface IGarbageCollector {
335
351
  headerData?: RuntimeHeaderData,
336
352
  ): void;
337
353
  /** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
338
- addedOutboundReference(fromNodePath: string, toNodePath: string): void;
354
+ addedOutboundReference(fromNodePath: string, toNodePath: string, autorecovery?: true): void;
339
355
  /** Called to process a garbage collection message. */
340
356
  processMessage(message: ContainerRuntimeGCMessage, local: boolean): void;
341
357
  /** Returns true if this node has been deleted by GC during sweep phase. */
@@ -51,6 +51,18 @@ export class GCSummaryStateTracker {
51
51
  // to unreferenced or vice-versa.
52
52
  public updatedDSCountSinceLastSummary: number = 0;
53
53
 
54
+ /** API for ensuring the correct auto-recovery mitigations */
55
+ public autoRecovery = {
56
+ requestFullGCOnNextRun: () => {
57
+ this.fullGCModeForAutoRecovery = true;
58
+ },
59
+ fullGCRequested: () => {
60
+ return this.fullGCModeForAutoRecovery;
61
+ },
62
+ };
63
+ /** If true, the next GC run will do fullGC mode to regenerate the GC data for each node */
64
+ private fullGCModeForAutoRecovery: boolean = false;
65
+
54
66
  constructor(
55
67
  // Tells whether GC should run or not.
56
68
  private readonly configs: Pick<
@@ -267,7 +279,7 @@ export class GCSummaryStateTracker {
267
279
  }
268
280
 
269
281
  /**
270
- * Called to refresh the latest summary state. This happens when either a pending summary is acked.
282
+ * Called to refresh the latest summary state. This happens when a pending summary is acked.
271
283
  */
272
284
  public async refreshLatestSummary(result: IRefreshSummaryResult): Promise<void> {
273
285
  if (!result.isSummaryTracked) {
@@ -287,6 +299,7 @@ export class GCSummaryStateTracker {
287
299
  this.latestSummaryData = this.pendingSummaryData;
288
300
  this.pendingSummaryData = undefined;
289
301
  this.updatedDSCountSinceLastSummary = 0;
302
+ this.fullGCModeForAutoRecovery = false;
290
303
  return;
291
304
  }
292
305
 
@@ -63,6 +63,7 @@ interface INodeUsageProps extends ICommonProps {
63
63
  currentReferenceTimestampMs: number | undefined;
64
64
  packagePath: readonly string[] | undefined;
65
65
  fromId?: string;
66
+ autorecovery?: true;
66
67
  }
67
68
 
68
69
  /**
package/src/gc/index.ts CHANGED
@@ -32,10 +32,13 @@ export {
32
32
  runSessionExpiryKey,
33
33
  runSweepKey,
34
34
  stableGCVersion,
35
+ disableAutoRecoveryKey,
35
36
  disableDatastoreSweepKey,
36
37
  UnreferencedState,
37
38
  throwOnTombstoneLoadOverrideKey,
38
39
  GarbageCollectionMessage,
40
+ GarbageCollectionMessageType,
41
+ ISweepMessage,
39
42
  } from "./gcDefinitions";
40
43
  export {
41
44
  cloneGCData,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.8.0.7";
9
+ export const pkgVersion = "2.0.0-internal.8.0.8";