@fluidframework/container-runtime 2.0.0-internal.2.0.3 → 2.0.0-internal.2.1.0

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 (78) hide show
  1. package/dist/containerRuntime.d.ts +2 -1
  2. package/dist/containerRuntime.d.ts.map +1 -1
  3. package/dist/containerRuntime.js +63 -23
  4. package/dist/containerRuntime.js.map +1 -1
  5. package/dist/dataStore.d.ts.map +1 -1
  6. package/dist/dataStore.js +6 -0
  7. package/dist/dataStore.js.map +1 -1
  8. package/dist/dataStoreContext.d.ts +7 -0
  9. package/dist/dataStoreContext.d.ts.map +1 -1
  10. package/dist/dataStoreContext.js +34 -8
  11. package/dist/dataStoreContext.js.map +1 -1
  12. package/dist/dataStoreContexts.js +1 -1
  13. package/dist/dataStoreContexts.js.map +1 -1
  14. package/dist/dataStores.d.ts +3 -2
  15. package/dist/dataStores.d.ts.map +1 -1
  16. package/dist/dataStores.js +29 -3
  17. package/dist/dataStores.js.map +1 -1
  18. package/dist/garbageCollection.d.ts +19 -5
  19. package/dist/garbageCollection.d.ts.map +1 -1
  20. package/dist/garbageCollection.js +120 -37
  21. package/dist/garbageCollection.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/packageVersion.d.ts +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/summarizerClientElection.js +1 -1
  30. package/dist/summarizerClientElection.js.map +1 -1
  31. package/dist/summaryGenerator.d.ts.map +1 -1
  32. package/dist/summaryGenerator.js +3 -2
  33. package/dist/summaryGenerator.js.map +1 -1
  34. package/garbageCollection.md +27 -22
  35. package/lib/containerRuntime.d.ts +2 -1
  36. package/lib/containerRuntime.d.ts.map +1 -1
  37. package/lib/containerRuntime.js +64 -24
  38. package/lib/containerRuntime.js.map +1 -1
  39. package/lib/dataStore.d.ts.map +1 -1
  40. package/lib/dataStore.js +6 -0
  41. package/lib/dataStore.js.map +1 -1
  42. package/lib/dataStoreContext.d.ts +7 -0
  43. package/lib/dataStoreContext.d.ts.map +1 -1
  44. package/lib/dataStoreContext.js +35 -9
  45. package/lib/dataStoreContext.js.map +1 -1
  46. package/lib/dataStoreContexts.js +1 -1
  47. package/lib/dataStoreContexts.js.map +1 -1
  48. package/lib/dataStores.d.ts +3 -2
  49. package/lib/dataStores.d.ts.map +1 -1
  50. package/lib/dataStores.js +30 -4
  51. package/lib/dataStores.js.map +1 -1
  52. package/lib/garbageCollection.d.ts +19 -5
  53. package/lib/garbageCollection.d.ts.map +1 -1
  54. package/lib/garbageCollection.js +119 -36
  55. package/lib/garbageCollection.js.map +1 -1
  56. package/lib/index.d.ts +1 -1
  57. package/lib/index.d.ts.map +1 -1
  58. package/lib/index.js +1 -1
  59. package/lib/index.js.map +1 -1
  60. package/lib/packageVersion.d.ts +1 -1
  61. package/lib/packageVersion.js +1 -1
  62. package/lib/packageVersion.js.map +1 -1
  63. package/lib/summarizerClientElection.js +1 -1
  64. package/lib/summarizerClientElection.js.map +1 -1
  65. package/lib/summaryGenerator.d.ts.map +1 -1
  66. package/lib/summaryGenerator.js +3 -2
  67. package/lib/summaryGenerator.js.map +1 -1
  68. package/package.json +26 -23
  69. package/src/containerRuntime.ts +77 -26
  70. package/src/dataStore.ts +13 -1
  71. package/src/dataStoreContext.ts +48 -10
  72. package/src/dataStoreContexts.ts +1 -1
  73. package/src/dataStores.ts +34 -3
  74. package/src/garbageCollection.ts +144 -44
  75. package/src/index.ts +1 -1
  76. package/src/packageVersion.ts +1 -1
  77. package/src/summarizerClientElection.ts +1 -1
  78. package/src/summaryGenerator.ts +3 -2
@@ -30,6 +30,8 @@ const GCVersion = 1;
30
30
  export const gcTreeKey = "gc";
31
31
  // They prefix for GC blobs in the GC tree in summary.
32
32
  export const gcBlobPrefix = "__gc";
33
+ // The key for tombstone blob in the GC tree in summary.
34
+ export const gcTombstoneBlobKey = "__tombstones";
33
35
  // Feature gate key to turn GC on / off.
34
36
  export const runGCKey = "Fluid.GarbageCollection.RunGC";
35
37
  // Feature gate key to turn GC sweep on / off.
@@ -42,6 +44,8 @@ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
42
44
  export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
43
45
  // Feature gate key to turn GC sweep log off.
44
46
  export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
47
+ // Feature gate key to tombstone datastores.
48
+ export const testTombstoneKey = "Fluid.GarbageCollection.Test.Tombstone";
45
49
  // One day in milliseconds.
46
50
  export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
47
51
  export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
@@ -157,7 +161,7 @@ export class UnreferencedStateTracker {
157
161
  */
158
162
  export class GarbageCollector {
159
163
  constructor(createParams) {
160
- var _a, _b, _c, _d, _e, _f, _g, _h;
164
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
161
165
  /**
162
166
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
163
167
  *
@@ -176,6 +180,7 @@ export class GarbageCollector {
176
180
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
177
181
  // outbound routes from that node.
178
182
  this.newReferencesSinceLastRun = new Map();
183
+ this.tombstones = [];
179
184
  // Map of node ids to their unreferenced state tracker.
180
185
  this.unreferencedNodesState = new Map();
181
186
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -297,6 +302,7 @@ export class GarbageCollector {
297
302
  }
298
303
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
299
304
  this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
305
+ this.tombstoneMode = (_j = this.mc.config.getBoolean(testTombstoneKey)) !== null && _j !== void 0 ? _j : false;
300
306
  // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
301
307
  // contain GC tree and GC is enabled.
302
308
  const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
@@ -312,11 +318,17 @@ export class GarbageCollector {
312
318
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
313
319
  const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
314
320
  if (gcSnapshotTree !== undefined) {
315
- const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
321
+ const baseGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
322
+ if (baseGCData.tombstones !== undefined && this.tombstoneMode) {
323
+ this.tombstones = baseGCData.tombstones;
324
+ }
316
325
  if (this.trackGCState) {
317
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
326
+ this.latestSummaryData = {
327
+ serializedGCState: JSON.stringify(generateSortedGCState(baseGCData.gcState)),
328
+ serializedTombstones: JSON.stringify(baseGCData.tombstones),
329
+ };
318
330
  }
319
- return baseGCState;
331
+ return baseGCData.gcState;
320
332
  }
321
333
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
322
334
  // consolidate into IGarbageCollectionState format.
@@ -461,7 +473,7 @@ export class GarbageCollector {
461
473
  }
462
474
  /** Returns a list of all the configurations for garbage collection. */
463
475
  get configs() {
464
- return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, sweepTimeout: this.sweepTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
476
+ return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, testMode: this.testMode, tombstoneMode: this.tombstoneMode, sessionExpiry: this.sessionExpiryTimeoutMs, sweepTimeout: this.sweepTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
465
477
  }
466
478
  /**
467
479
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -471,15 +483,19 @@ export class GarbageCollector {
471
483
  */
472
484
  setConnectionState(connected, clientId) {
473
485
  /**
474
- * For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
486
+ * For all clients, initialize the base state when the container becomes active, i.e., it transitions
475
487
  * to "write" mode. This will ensure that the container's own join op is processed and there is a recent
476
488
  * reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
477
489
  * could affect the GC state will have been processed.
478
490
  *
491
+ * If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
492
+ * InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
493
+ * the receiving summarizer client.
494
+ *
479
495
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
480
496
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
481
497
  */
482
- if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
498
+ if (this.activeConnection() && this.shouldRunGC) {
483
499
  this.initializeBaseStateP.catch((error) => { });
484
500
  }
485
501
  }
@@ -545,6 +561,22 @@ export class GarbageCollector {
545
561
  if (this.testMode) {
546
562
  this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
547
563
  }
564
+ else {
565
+ // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
566
+ // scenarios involving access to "deleted" data without actually deleting the data from summaries.
567
+ // Note: we will not tombstone in test mode
568
+ if (this.tombstoneMode) {
569
+ const tombstoneRoutes = [];
570
+ // Currently only tombstone datastores
571
+ for (const [key, value] of this.unreferencedNodesState.entries()) {
572
+ if (value.state === UnreferencedState.SweepReady &&
573
+ this.runtime.getNodeType(key) === GCNodeType.DataStore) {
574
+ tombstoneRoutes.push(key);
575
+ }
576
+ }
577
+ this.runtime.deleteUnusedRoutes(tombstoneRoutes);
578
+ }
579
+ }
548
580
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
549
581
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
550
582
  // reference to an unreferenced node from another unreferenced node which means the node wasn't revived.
@@ -568,31 +600,67 @@ export class GarbageCollector {
568
600
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
569
601
  };
570
602
  }
571
- const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
603
+ const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
604
+ const serializedTombstones = this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined;
572
605
  /**
573
- * As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
574
- * instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
606
+ * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
607
+ * summary, send summary handles for them. Otherwise, send the data in summary blobs.
575
608
  */
576
609
  if (this.trackGCState) {
577
- this.pendingSerializedSummaryState = newSerializedSummaryState;
578
- if (this.latestSerializedSummaryState !== undefined &&
579
- this.latestSerializedSummaryState === newSerializedSummaryState &&
580
- !fullTree &&
581
- trackState) {
582
- const stats = mergeStats();
583
- stats.handleNodeCount++;
584
- return {
585
- summary: {
586
- type: SummaryType.Handle,
587
- handle: `/${gcTreeKey}`,
588
- handleType: SummaryType.Tree,
589
- },
590
- stats,
591
- };
610
+ this.pendingSummaryData = { serializedGCState, serializedTombstones };
611
+ if (trackState && !fullTree && this.latestSummaryData !== undefined) {
612
+ // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
613
+ if (this.latestSummaryData.serializedGCState === serializedGCState
614
+ && this.latestSummaryData.serializedTombstones === serializedTombstones) {
615
+ const stats = mergeStats();
616
+ stats.handleNodeCount++;
617
+ return {
618
+ summary: {
619
+ type: SummaryType.Handle,
620
+ handle: `/${gcTreeKey}`,
621
+ handleType: SummaryType.Tree,
622
+ },
623
+ stats,
624
+ };
625
+ }
626
+ // If either or both of GC state or tombstone state changed, build a GC summary tree.
627
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
592
628
  }
593
629
  }
630
+ // If not tracking GC state, build a GC summary tree without any summary handles.
631
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
632
+ }
633
+ /**
634
+ * Builds the GC summary tree which contains GC state and tombstone state.
635
+ * If trackState is false, both GC state and tombstone state are written as summary blobs.
636
+ * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
637
+ * @param serializedGCState - The GC state serialized as string.
638
+ * @param serializedTombstones - THe tombstone state serialized as string.
639
+ * @param trackState - Whether we are tracking GC state across summaries.
640
+ * @returns the GC summary tree.
641
+ */
642
+ buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
643
+ var _a, _b;
644
+ const gcStateBlobKey = `${gcBlobPrefix}_root`;
594
645
  const builder = new SummaryTreeBuilder();
595
- builder.addBlob(`${gcBlobPrefix}_root`, newSerializedSummaryState);
646
+ // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
647
+ if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
648
+ builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
649
+ }
650
+ else {
651
+ builder.addBlob(gcStateBlobKey, serializedGCState);
652
+ }
653
+ // If there is no tombstone data, return only the GC state.
654
+ if (serializedTombstones === undefined) {
655
+ return builder.getSummaryTree();
656
+ }
657
+ // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
658
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
659
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
660
+ }
661
+ else {
662
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
663
+ }
596
664
  return builder.getSummaryTree();
597
665
  }
598
666
  getMetadata() {
@@ -628,8 +696,8 @@ export class GarbageCollector {
628
696
  this.latestSummaryGCVersion = this.currentGCVersion;
629
697
  this.initialStateNeedsReset = false;
630
698
  if (this.trackGCState) {
631
- this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
632
- this.pendingSerializedSummaryState = undefined;
699
+ this.latestSummaryData = this.pendingSummaryData;
700
+ this.pendingSummaryData = undefined;
633
701
  }
634
702
  return;
635
703
  }
@@ -643,13 +711,16 @@ export class GarbageCollector {
643
711
  }
644
712
  const gcSnapshotTree = snapshot.trees[gcTreeKey];
645
713
  if (gcSnapshotTree !== undefined && this.trackGCState) {
646
- const latestGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
647
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
714
+ const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
715
+ this.latestSummaryData = {
716
+ serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
717
+ serializedTombstones: JSON.stringify(latestGCData.tombstones),
718
+ };
648
719
  }
649
720
  else {
650
- this.latestSerializedSummaryState = undefined;
721
+ this.latestSummaryData = undefined;
651
722
  }
652
- this.pendingSerializedSummaryState = undefined;
723
+ this.pendingSummaryData = undefined;
653
724
  }
654
725
  /**
655
726
  * Called when a node with the given id is updated. If the node is inactive, log an error.
@@ -704,6 +775,7 @@ export class GarbageCollector {
704
775
  */
705
776
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
706
777
  this.previousGCDataFromLastRun = cloneGCData(gcData);
778
+ this.tombstones = [];
707
779
  this.newReferencesSinceLastRun.clear();
708
780
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
709
781
  for (const nodeId of gcResult.referencedNodeIds) {
@@ -727,6 +799,12 @@ export class GarbageCollector {
727
799
  }
728
800
  else {
729
801
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
802
+ if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
803
+ const nodeType = this.runtime.getNodeType(nodeId);
804
+ if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
805
+ this.tombstones.push(nodeId);
806
+ }
807
+ }
730
808
  }
731
809
  }
732
810
  }
@@ -1011,12 +1089,17 @@ export class GarbageCollector {
1011
1089
  }
1012
1090
  }
1013
1091
  /**
1014
- * Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
1015
- * Merge the GC state from all such blobs and return the merged GC state.
1092
+ * Gets the garbage collection data from the given snapshot tree. It contains GC state and tombstone state.
1093
+ * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1016
1094
  */
1017
- async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1095
+ async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1018
1096
  let rootGCState = { gcNodes: {} };
1097
+ let tombstones;
1019
1098
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1099
+ if (key === gcTombstoneBlobKey) {
1100
+ tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1101
+ continue;
1102
+ }
1020
1103
  // Skip blobs that do not start with the GC prefix.
1021
1104
  if (!key.startsWith(gcBlobPrefix)) {
1022
1105
  continue;
@@ -1030,7 +1113,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1030
1113
  // Merge the GC state of this blob into the root GC state.
1031
1114
  rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
1032
1115
  }
1033
- return rootGCState;
1116
+ return { gcState: rootGCState, tombstones };
1034
1117
  }
1035
1118
  function generateSortedGCState(gcState) {
1036
1119
  const sortableArray = Object.entries(gcState.gcNodes);