@fluidframework/container-runtime 2.0.0-internal.2.0.4 → 2.0.0-internal.2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -15,7 +15,7 @@ var __rest = (this && this.__rest) || function (s, e) {
15
15
  return t;
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.disableSweepLogKey = exports.trackGCStateKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
18
+ exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.testTombstoneKey = exports.disableSweepLogKey = exports.trackGCStateKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcTombstoneBlobKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
19
19
  const common_utils_1 = require("@fluidframework/common-utils");
20
20
  const container_utils_1 = require("@fluidframework/container-utils");
21
21
  const garbage_collector_1 = require("@fluidframework/garbage-collector");
@@ -33,6 +33,8 @@ const GCVersion = 1;
33
33
  exports.gcTreeKey = "gc";
34
34
  // They prefix for GC blobs in the GC tree in summary.
35
35
  exports.gcBlobPrefix = "__gc";
36
+ // The key for tombstone blob in the GC tree in summary.
37
+ exports.gcTombstoneBlobKey = "__tombstones";
36
38
  // Feature gate key to turn GC on / off.
37
39
  exports.runGCKey = "Fluid.GarbageCollection.RunGC";
38
40
  // Feature gate key to turn GC sweep on / off.
@@ -45,6 +47,8 @@ exports.runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
45
47
  exports.trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
46
48
  // Feature gate key to turn GC sweep log off.
47
49
  exports.disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
50
+ // Feature gate key to tombstone datastores.
51
+ exports.testTombstoneKey = "Fluid.GarbageCollection.Test.Tombstone";
48
52
  // One day in milliseconds.
49
53
  exports.oneDayMs = 1 * 24 * 60 * 60 * 1000;
50
54
  exports.defaultInactiveTimeoutMs = 7 * exports.oneDayMs; // 7 days
@@ -161,7 +165,7 @@ exports.UnreferencedStateTracker = UnreferencedStateTracker;
161
165
  */
162
166
  class GarbageCollector {
163
167
  constructor(createParams) {
164
- var _a, _b, _c, _d, _e, _f, _g, _h;
168
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
165
169
  /**
166
170
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
167
171
  *
@@ -180,6 +184,7 @@ class GarbageCollector {
180
184
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
181
185
  // outbound routes from that node.
182
186
  this.newReferencesSinceLastRun = new Map();
187
+ this.tombstones = [];
183
188
  // Map of node ids to their unreferenced state tracker.
184
189
  this.unreferencedNodesState = new Map();
185
190
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -301,6 +306,7 @@ class GarbageCollector {
301
306
  }
302
307
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
303
308
  this.testMode = (_h = this.mc.config.getBoolean(exports.gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
309
+ this.tombstoneMode = (_j = this.mc.config.getBoolean(exports.testTombstoneKey)) !== null && _j !== void 0 ? _j : false;
304
310
  // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
305
311
  // contain GC tree and GC is enabled.
306
312
  const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[exports.gcTreeKey]) !== undefined;
@@ -316,11 +322,17 @@ class GarbageCollector {
316
322
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
317
323
  const gcSnapshotTree = baseSnapshot.trees[exports.gcTreeKey];
318
324
  if (gcSnapshotTree !== undefined) {
319
- const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
325
+ const baseGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
326
+ if (baseGCData.tombstones !== undefined && this.tombstoneMode) {
327
+ this.tombstones = baseGCData.tombstones;
328
+ }
320
329
  if (this.trackGCState) {
321
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
330
+ this.latestSummaryData = {
331
+ serializedGCState: JSON.stringify(generateSortedGCState(baseGCData.gcState)),
332
+ serializedTombstones: JSON.stringify(baseGCData.tombstones),
333
+ };
322
334
  }
323
- return baseGCState;
335
+ return baseGCData.gcState;
324
336
  }
325
337
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
326
338
  // consolidate into IGarbageCollectionState format.
@@ -465,7 +477,7 @@ class GarbageCollector {
465
477
  }
466
478
  /** Returns a list of all the configurations for garbage collection. */
467
479
  get configs() {
468
- 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);
480
+ 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);
469
481
  }
470
482
  /**
471
483
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
@@ -475,15 +487,19 @@ class GarbageCollector {
475
487
  */
476
488
  setConnectionState(connected, clientId) {
477
489
  /**
478
- * For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
490
+ * For all clients, initialize the base state when the container becomes active, i.e., it transitions
479
491
  * to "write" mode. This will ensure that the container's own join op is processed and there is a recent
480
492
  * reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
481
493
  * could affect the GC state will have been processed.
482
494
  *
495
+ * If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
496
+ * InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
497
+ * the receiving summarizer client.
498
+ *
483
499
  * Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
484
500
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
485
501
  */
486
- if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
502
+ if (this.activeConnection() && this.shouldRunGC) {
487
503
  this.initializeBaseStateP.catch((error) => { });
488
504
  }
489
505
  }
@@ -549,6 +565,22 @@ class GarbageCollector {
549
565
  if (this.testMode) {
550
566
  this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
551
567
  }
568
+ else {
569
+ // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
570
+ // scenarios involving access to "deleted" data without actually deleting the data from summaries.
571
+ // Note: we will not tombstone in test mode
572
+ if (this.tombstoneMode) {
573
+ const tombstoneRoutes = [];
574
+ // Currently only tombstone datastores
575
+ for (const [key, value] of this.unreferencedNodesState.entries()) {
576
+ if (value.state === exports.UnreferencedState.SweepReady &&
577
+ this.runtime.getNodeType(key) === exports.GCNodeType.DataStore) {
578
+ tombstoneRoutes.push(key);
579
+ }
580
+ }
581
+ this.runtime.deleteUnusedRoutes(tombstoneRoutes);
582
+ }
583
+ }
552
584
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
553
585
  // updates its state so that we don't send false positives based on intermediate state. For example, we may get
554
586
  // reference to an unreferenced node from another unreferenced node which means the node wasn't revived.
@@ -572,31 +604,67 @@ class GarbageCollector {
572
604
  unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
573
605
  };
574
606
  }
575
- const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
607
+ const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
608
+ const serializedTombstones = this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined;
576
609
  /**
577
- * As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
578
- * instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
610
+ * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
611
+ * summary, send summary handles for them. Otherwise, send the data in summary blobs.
579
612
  */
580
613
  if (this.trackGCState) {
581
- this.pendingSerializedSummaryState = newSerializedSummaryState;
582
- if (this.latestSerializedSummaryState !== undefined &&
583
- this.latestSerializedSummaryState === newSerializedSummaryState &&
584
- !fullTree &&
585
- trackState) {
586
- const stats = (0, runtime_utils_1.mergeStats)();
587
- stats.handleNodeCount++;
588
- return {
589
- summary: {
590
- type: protocol_definitions_1.SummaryType.Handle,
591
- handle: `/${exports.gcTreeKey}`,
592
- handleType: protocol_definitions_1.SummaryType.Tree,
593
- },
594
- stats,
595
- };
614
+ this.pendingSummaryData = { serializedGCState, serializedTombstones };
615
+ if (trackState && !fullTree && this.latestSummaryData !== undefined) {
616
+ // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
617
+ if (this.latestSummaryData.serializedGCState === serializedGCState
618
+ && this.latestSummaryData.serializedTombstones === serializedTombstones) {
619
+ const stats = (0, runtime_utils_1.mergeStats)();
620
+ stats.handleNodeCount++;
621
+ return {
622
+ summary: {
623
+ type: protocol_definitions_1.SummaryType.Handle,
624
+ handle: `/${exports.gcTreeKey}`,
625
+ handleType: protocol_definitions_1.SummaryType.Tree,
626
+ },
627
+ stats,
628
+ };
629
+ }
630
+ // If either or both of GC state or tombstone state changed, build a GC summary tree.
631
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
596
632
  }
597
633
  }
634
+ // If not tracking GC state, build a GC summary tree without any summary handles.
635
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
636
+ }
637
+ /**
638
+ * Builds the GC summary tree which contains GC state and tombstone state.
639
+ * If trackState is false, both GC state and tombstone state are written as summary blobs.
640
+ * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
641
+ * @param serializedGCState - The GC state serialized as string.
642
+ * @param serializedTombstones - THe tombstone state serialized as string.
643
+ * @param trackState - Whether we are tracking GC state across summaries.
644
+ * @returns the GC summary tree.
645
+ */
646
+ buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
647
+ var _a, _b;
648
+ const gcStateBlobKey = `${exports.gcBlobPrefix}_root`;
598
649
  const builder = new runtime_utils_1.SummaryTreeBuilder();
599
- builder.addBlob(`${exports.gcBlobPrefix}_root`, newSerializedSummaryState);
650
+ // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
651
+ if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
652
+ builder.addHandle(gcStateBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${gcStateBlobKey}`);
653
+ }
654
+ else {
655
+ builder.addBlob(gcStateBlobKey, serializedGCState);
656
+ }
657
+ // If there is no tombstone data, return only the GC state.
658
+ if (serializedTombstones === undefined) {
659
+ return builder.getSummaryTree();
660
+ }
661
+ // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
662
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
663
+ builder.addHandle(exports.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${exports.gcTombstoneBlobKey}`);
664
+ }
665
+ else {
666
+ builder.addBlob(exports.gcTombstoneBlobKey, serializedTombstones);
667
+ }
600
668
  return builder.getSummaryTree();
601
669
  }
602
670
  getMetadata() {
@@ -632,8 +700,8 @@ class GarbageCollector {
632
700
  this.latestSummaryGCVersion = this.currentGCVersion;
633
701
  this.initialStateNeedsReset = false;
634
702
  if (this.trackGCState) {
635
- this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
636
- this.pendingSerializedSummaryState = undefined;
703
+ this.latestSummaryData = this.pendingSummaryData;
704
+ this.pendingSummaryData = undefined;
637
705
  }
638
706
  return;
639
707
  }
@@ -647,13 +715,16 @@ class GarbageCollector {
647
715
  }
648
716
  const gcSnapshotTree = snapshot.trees[exports.gcTreeKey];
649
717
  if (gcSnapshotTree !== undefined && this.trackGCState) {
650
- const latestGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
651
- this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
718
+ const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
719
+ this.latestSummaryData = {
720
+ serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
721
+ serializedTombstones: JSON.stringify(latestGCData.tombstones),
722
+ };
652
723
  }
653
724
  else {
654
- this.latestSerializedSummaryState = undefined;
725
+ this.latestSummaryData = undefined;
655
726
  }
656
- this.pendingSerializedSummaryState = undefined;
727
+ this.pendingSummaryData = undefined;
657
728
  }
658
729
  /**
659
730
  * Called when a node with the given id is updated. If the node is inactive, log an error.
@@ -708,6 +779,7 @@ class GarbageCollector {
708
779
  */
709
780
  updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
710
781
  this.previousGCDataFromLastRun = (0, garbage_collector_1.cloneGCData)(gcData);
782
+ this.tombstones = [];
711
783
  this.newReferencesSinceLastRun.clear();
712
784
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
713
785
  for (const nodeId of gcResult.referencedNodeIds) {
@@ -731,6 +803,12 @@ class GarbageCollector {
731
803
  }
732
804
  else {
733
805
  nodeStateTracker.updateTracking(currentReferenceTimestampMs);
806
+ if (this.tombstoneMode && nodeStateTracker.state === exports.UnreferencedState.SweepReady) {
807
+ const nodeType = this.runtime.getNodeType(nodeId);
808
+ if (nodeType === exports.GCNodeType.DataStore || nodeType === exports.GCNodeType.Blob) {
809
+ this.tombstones.push(nodeId);
810
+ }
811
+ }
734
812
  }
735
813
  }
736
814
  }
@@ -1016,12 +1094,17 @@ class GarbageCollector {
1016
1094
  }
1017
1095
  exports.GarbageCollector = GarbageCollector;
1018
1096
  /**
1019
- * Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
1020
- * Merge the GC state from all such blobs and return the merged GC state.
1097
+ * Gets the garbage collection data from the given snapshot tree. It contains GC state and tombstone state.
1098
+ * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1021
1099
  */
1022
- async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1100
+ async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1023
1101
  let rootGCState = { gcNodes: {} };
1102
+ let tombstones;
1024
1103
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1104
+ if (key === exports.gcTombstoneBlobKey) {
1105
+ tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
1106
+ continue;
1107
+ }
1025
1108
  // Skip blobs that do not start with the GC prefix.
1026
1109
  if (!key.startsWith(exports.gcBlobPrefix)) {
1027
1110
  continue;
@@ -1035,7 +1118,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
1035
1118
  // Merge the GC state of this blob into the root GC state.
1036
1119
  rootGCState = (0, garbage_collector_1.concatGarbageCollectionStates)(rootGCState, gcState);
1037
1120
  }
1038
- return rootGCState;
1121
+ return { gcState: rootGCState, tombstones };
1039
1122
  }
1040
1123
  function generateSortedGCState(gcState) {
1041
1124
  const sortableArray = Object.entries(gcState.gcNodes);