@fluidframework/container-runtime 2.0.0-internal.2.3.0 → 2.0.0-internal.2.4.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 (81) hide show
  1. package/dist/blobManager.d.ts +4 -2
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +43 -10
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +15 -0
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +28 -13
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts.map +1 -1
  10. package/dist/dataStoreContext.js +5 -8
  11. package/dist/dataStoreContext.js.map +1 -1
  12. package/dist/dataStores.d.ts +3 -3
  13. package/dist/dataStores.d.ts.map +1 -1
  14. package/dist/dataStores.js +14 -14
  15. package/dist/dataStores.js.map +1 -1
  16. package/dist/garbageCollection.d.ts +15 -7
  17. package/dist/garbageCollection.d.ts.map +1 -1
  18. package/dist/garbageCollection.js +101 -40
  19. package/dist/garbageCollection.js.map +1 -1
  20. package/dist/garbageCollectionConstants.d.ts +1 -0
  21. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  22. package/dist/garbageCollectionConstants.js +4 -2
  23. package/dist/garbageCollectionConstants.js.map +1 -1
  24. package/dist/garbageCollectionTombstoneUtils.d.ts +5 -4
  25. package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -1
  26. package/dist/garbageCollectionTombstoneUtils.js +8 -13
  27. package/dist/garbageCollectionTombstoneUtils.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/packageVersion.d.ts +1 -1
  33. package/dist/packageVersion.js +1 -1
  34. package/dist/packageVersion.js.map +1 -1
  35. package/dist/summarizer.js.map +1 -1
  36. package/lib/blobManager.d.ts +4 -2
  37. package/lib/blobManager.d.ts.map +1 -1
  38. package/lib/blobManager.js +44 -11
  39. package/lib/blobManager.js.map +1 -1
  40. package/lib/containerRuntime.d.ts +15 -0
  41. package/lib/containerRuntime.d.ts.map +1 -1
  42. package/lib/containerRuntime.js +27 -12
  43. package/lib/containerRuntime.js.map +1 -1
  44. package/lib/dataStoreContext.d.ts.map +1 -1
  45. package/lib/dataStoreContext.js +5 -8
  46. package/lib/dataStoreContext.js.map +1 -1
  47. package/lib/dataStores.d.ts +3 -3
  48. package/lib/dataStores.d.ts.map +1 -1
  49. package/lib/dataStores.js +15 -15
  50. package/lib/dataStores.js.map +1 -1
  51. package/lib/garbageCollection.d.ts +15 -7
  52. package/lib/garbageCollection.d.ts.map +1 -1
  53. package/lib/garbageCollection.js +103 -42
  54. package/lib/garbageCollection.js.map +1 -1
  55. package/lib/garbageCollectionConstants.d.ts +1 -0
  56. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  57. package/lib/garbageCollectionConstants.js +3 -1
  58. package/lib/garbageCollectionConstants.js.map +1 -1
  59. package/lib/garbageCollectionTombstoneUtils.d.ts +5 -4
  60. package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -1
  61. package/lib/garbageCollectionTombstoneUtils.js +9 -14
  62. package/lib/garbageCollectionTombstoneUtils.js.map +1 -1
  63. package/lib/index.d.ts +1 -1
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js +1 -1
  66. package/lib/index.js.map +1 -1
  67. package/lib/packageVersion.d.ts +1 -1
  68. package/lib/packageVersion.js +1 -1
  69. package/lib/packageVersion.js.map +1 -1
  70. package/lib/summarizer.js.map +1 -1
  71. package/package.json +16 -16
  72. package/src/blobManager.ts +57 -13
  73. package/src/containerRuntime.ts +38 -9
  74. package/src/dataStoreContext.ts +13 -14
  75. package/src/dataStores.ts +25 -19
  76. package/src/garbageCollection.ts +115 -42
  77. package/src/garbageCollectionConstants.ts +3 -1
  78. package/src/garbageCollectionTombstoneUtils.ts +11 -14
  79. package/src/index.ts +2 -0
  80. package/src/packageVersion.ts +1 -1
  81. package/src/summarizer.ts +1 -1
@@ -17,12 +17,13 @@ import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
17
17
  import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
18
18
  import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot, runGarbageCollection, trimLeadingSlashes, } from "@fluidframework/garbage-collector";
19
19
  import { SummaryType } from "@fluidframework/protocol-definitions";
20
- import { gcTreeKey, gcBlobPrefix, gcTombstoneBlobKey, } from "@fluidframework/runtime-definitions";
20
+ import { gcTreeKey, gcBlobPrefix, gcTombstoneBlobKey, gcDeletedBlobKey, } from "@fluidframework/runtime-definitions";
21
21
  import { mergeStats, packagePathToTelemetryProperty, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
22
22
  import { ChildLogger, generateStack, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
23
23
  import { RuntimeHeaders } from "./containerRuntime";
24
24
  import { getSummaryForDatastores } from "./dataStores";
25
- import { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, throwOnTombstoneUsageKey, trackGCStateKey } from "./garbageCollectionConstants";
25
+ import { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, trackGCStateKey } from "./garbageCollectionConstants";
26
+ import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
26
27
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
27
28
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
28
29
  /** The types of GC nodes in the GC reference graph. */
@@ -140,7 +141,10 @@ export class GarbageCollector {
140
141
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
141
142
  // outbound routes from that node.
142
143
  this.newReferencesSinceLastRun = new Map();
144
+ // A list of nodes that have been tombstoned.
143
145
  this.tombstones = [];
146
+ // A list of nodes that have been deleted during sweep phase.
147
+ this.deletedNodes = new Set();
144
148
  // Map of node ids to their unreferenced state tracker.
145
149
  this.unreferencedNodesState = new Map();
146
150
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -209,8 +213,7 @@ export class GarbageCollector {
209
213
  // flag in GC options to false.
210
214
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
211
215
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
212
- // ...unless we're using the TestOverride
213
- this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
216
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
214
217
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
215
218
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
216
219
  this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
@@ -266,8 +269,9 @@ export class GarbageCollector {
266
269
  }
267
270
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
268
271
  this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
269
- // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
270
- this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
272
+ // Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
273
+ // via feature flags.
274
+ this.tombstoneMode = !this.shouldRunSweep && this.mc.config.getBoolean(disableTombstoneKey) !== true;
271
275
  // If GC ran in the container that generated the base snapshot, it will have a GC tree.
272
276
  this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
273
277
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
@@ -317,7 +321,9 @@ export class GarbageCollector {
317
321
  }
318
322
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
319
323
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
320
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
324
+ return Object.keys(gcState.gcNodes).length === 1
325
+ ? undefined
326
+ : { gcState, tombstones: undefined, deletedNodes: undefined };
321
327
  }
322
328
  catch (error) {
323
329
  const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -430,24 +436,31 @@ export class GarbageCollector {
430
436
  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);
431
437
  }
432
438
  /**
433
- * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
434
- * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
435
- * in use or not.
439
+ * Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
440
+ * during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
436
441
  */
437
442
  async initializeBaseState() {
438
443
  const baseSnapshotData = await this.baseSnapshotDataP;
439
444
  /**
440
- * The base snapshot data or tombstone state will not be present if the container is loaded from:
445
+ * The base snapshot data will not be present if the container is loaded from:
441
446
  * 1. The first summary created by the detached container.
442
447
  * 2. A summary that was generated with GC disabled.
443
448
  * 3. A summary that was generated before GC even existed.
444
- * 4. A summary that was generated with tombstone feature disabled.
445
449
  */
446
- if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
450
+ if (baseSnapshotData === undefined) {
447
451
  return;
448
452
  }
449
- this.tombstones = Array.from(baseSnapshotData.tombstones);
450
- this.runtime.updateTombstonedRoutes(this.tombstones);
453
+ // Initialize the deleted nodes from the snapshot. This is done irrespective of whether sweep is enabled or not
454
+ // to identify deleted nodes' usage.
455
+ if (baseSnapshotData.deletedNodes !== undefined) {
456
+ this.deletedNodes = new Set(baseSnapshotData.deletedNodes);
457
+ }
458
+ // If running in tombstone mode, initialize the tombstone state from the snapshot. Also, notify the runtime of
459
+ // tombstone routes.
460
+ if (this.tombstoneMode && baseSnapshotData.tombstones !== undefined) {
461
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
462
+ this.runtime.updateTombstonedRoutes(this.tombstones);
463
+ }
451
464
  }
452
465
  /**
453
466
  * Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
@@ -474,9 +487,30 @@ export class GarbageCollector {
474
487
  }
475
488
  ;
476
489
  this.unreferencedNodesState.clear();
477
- // If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
478
- // container as per the state in the snapshot data.
479
- if (this.tombstoneMode) {
490
+ // If running sweep, the tombstone state represents the list of nodes that have been deleted during sweep.
491
+ // If running in tombstone mode, the tombstone state represents the list of nodes that have been marked as
492
+ // tombstones.
493
+ // If this call is because we are refreshing from a snapshot due to an ack, it is likely that the GC state
494
+ // in the snapshot is newer than this client's. And so, the deleted / tombstone nodes need to be updated.
495
+ if (this.shouldRunSweep) {
496
+ const snapshotDeletedNodes = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? new Set(snapshotData.tombstones) : undefined;
497
+ // If the snapshot contains deleted nodes that are not yet deleted by this client, ask the runtime to
498
+ // delete them.
499
+ if (snapshotDeletedNodes !== undefined) {
500
+ const newDeletedNodes = [];
501
+ for (const nodeId of snapshotDeletedNodes) {
502
+ if (!this.deletedNodes.has(nodeId)) {
503
+ newDeletedNodes.push(nodeId);
504
+ }
505
+ }
506
+ if (newDeletedNodes.length > 0) {
507
+ // Call container runtime to delete these nodes and add deleted nodes to this.deletedNodes.
508
+ }
509
+ }
510
+ }
511
+ else if (this.tombstoneMode) {
512
+ // The snapshot may contain more or fewer tombstone nodes than this client. Update tombstone state and
513
+ // notify the runtime to update its state as well.
480
514
  this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
481
515
  this.runtime.updateTombstonedRoutes(this.tombstones);
482
516
  }
@@ -501,6 +535,7 @@ export class GarbageCollector {
501
535
  this.latestSummaryData = {
502
536
  serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
503
537
  serializedTombstones: JSON.stringify(snapshotData.tombstones),
538
+ serializedDeletedNodes: JSON.stringify(snapshotData.deletedNodes),
504
539
  };
505
540
  }
506
541
  }
@@ -620,17 +655,25 @@ export class GarbageCollector {
620
655
  };
621
656
  }
622
657
  const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
658
+ // Serialize and write deleted nodes, if any. This is done irrespective of whether sweep is enabled or not so
659
+ // to identify deleted nodes' usage.
660
+ const serializedDeletedNodes = this.deletedNodes.size > 0
661
+ ? JSON.stringify(Array.from(this.deletedNodes).sort())
662
+ : undefined;
663
+ // If running in tombstone mode, serialize and write tombstones, if any.
623
664
  const serializedTombstones = this.tombstoneMode
624
665
  ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
625
666
  : undefined;
626
667
  /**
627
- * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
628
- * summary, send summary handles for them. Otherwise, send the data in summary blobs.
668
+ * Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
669
+ * write summary handle instead of summary tree for GC.
670
+ * Otherwise, write the GC summary tree. In the tree, for each of these that changed, write a summary blob and
671
+ * for each of these that did not change, write a summary handle.
629
672
  */
630
673
  if (this.trackGCState) {
631
- this.pendingSummaryData = { serializedGCState, serializedTombstones };
674
+ this.pendingSummaryData = { serializedGCState, serializedTombstones, serializedDeletedNodes };
632
675
  if (trackState && !fullTree && this.latestSummaryData !== undefined) {
633
- // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
676
+ // If nothing changed since last summary, send a summary handle for the entire GC data.
634
677
  if (this.latestSummaryData.serializedGCState === serializedGCState
635
678
  && this.latestSummaryData.serializedTombstones === serializedTombstones) {
636
679
  const stats = mergeStats();
@@ -644,24 +687,25 @@ export class GarbageCollector {
644
687
  stats,
645
688
  };
646
689
  }
647
- // If either or both of GC state or tombstone state changed, build a GC summary tree.
648
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
690
+ // If some state changed, build a GC summary tree.
691
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
649
692
  }
650
693
  }
651
694
  // If not tracking GC state, build a GC summary tree without any summary handles.
652
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
695
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
653
696
  }
654
697
  /**
655
- * Builds the GC summary tree which contains GC state and tombstone state.
656
- * If trackState is false, both GC state and tombstone state are written as summary blobs.
657
- * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
698
+ * Builds the GC summary tree which contains GC state, deleted nodes and tombstones.
699
+ * If trackState is false, all of GC state, deleted nodes and tombstones are written as summary blobs.
700
+ * If trackState is true, only states that changed are written. Rest are written as handles.
658
701
  * @param serializedGCState - The GC state serialized as string.
659
- * @param serializedTombstones - THe tombstone state serialized as string.
702
+ * @param serializedTombstones - The tombstone state serialized as string.
703
+ * @param serializedDeletedNodes - Deleted nodes serialized as string.
660
704
  * @param trackState - Whether we are tracking GC state across summaries.
661
705
  * @returns the GC summary tree.
662
706
  */
663
- buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
664
- var _a, _b;
707
+ buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
708
+ var _a, _b, _c;
665
709
  const gcStateBlobKey = `${gcBlobPrefix}_root`;
666
710
  const builder = new SummaryTreeBuilder();
667
711
  // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
@@ -671,16 +715,26 @@ export class GarbageCollector {
671
715
  else {
672
716
  builder.addBlob(gcStateBlobKey, serializedGCState);
673
717
  }
674
- // If there is no tombstone data, return only the GC state.
675
- if (serializedTombstones === undefined) {
718
+ // If tombstones exist, write a summary handle if it hasn't changed. If it has changed, write a
719
+ // summary blob.
720
+ if (serializedTombstones !== undefined) {
721
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
722
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
723
+ }
724
+ else {
725
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
726
+ }
727
+ }
728
+ // If there are no deleted nodes, return the summary tree.
729
+ if (serializedDeletedNodes === undefined) {
676
730
  return builder.getSummaryTree();
677
731
  }
678
- // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
679
- if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
680
- builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
732
+ // If the deleted nodes hasn't changed, write a summary handle, else write a summary blob for it.
733
+ if (((_c = this.latestSummaryData) === null || _c === void 0 ? void 0 : _c.serializedDeletedNodes) === serializedDeletedNodes && trackState) {
734
+ builder.addHandle(gcDeletedBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcDeletedBlobKey}`);
681
735
  }
682
736
  else {
683
- builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
737
+ builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
684
738
  }
685
739
  return builder.getSummaryTree();
686
740
  }
@@ -776,7 +830,7 @@ export class GarbageCollector {
776
830
  * @param toNodePath - The node to which the reference is added.
777
831
  */
778
832
  addedOutboundReference(fromNodePath, toNodePath) {
779
- var _a, _b;
833
+ var _a;
780
834
  if (!this.shouldRunGC) {
781
835
  return;
782
836
  }
@@ -796,15 +850,22 @@ export class GarbageCollector {
796
850
  else if (nodeType === GCNodeType.Blob) {
797
851
  eventName = "GC_Tombstone_Blob_Revived";
798
852
  }
799
- this.mc.logger.sendTelemetryEvent({
853
+ sendGCTombstoneEvent(this.mc, {
800
854
  eventName,
855
+ category: "generic",
801
856
  isSummarizerClient: this.isSummarizerClient,
802
857
  url: trimLeadingSlashes(toNodePath),
803
858
  nodeType,
804
- throwOnTombstoneUsage: (_b = this.mc.config.getBoolean(throwOnTombstoneUsageKey)) !== null && _b !== void 0 ? _b : false,
805
- });
859
+ }, undefined /* packagePath */);
806
860
  }
807
861
  }
862
+ /**
863
+ * Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
864
+ * cases where objects are used after they are deleted and throw / log errors accordingly.
865
+ */
866
+ isNodeDeleted(nodePath) {
867
+ return this.deletedNodes.has(nodePath);
868
+ }
808
869
  dispose() {
809
870
  var _a;
810
871
  (_a = this.sessionExpiryTimer) === null || _a === void 0 ? void 0 : _a.clear();
@@ -1116,7 +1177,7 @@ export class GarbageCollector {
1116
1177
  this.mc.logger.sendErrorEvent(event);
1117
1178
  }
1118
1179
  }
1119
- // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
1180
+ // If SweepReady Usage Detection is enabled, the handler may close the interactive container.
1120
1181
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
1121
1182
  // and errors will arise elsewhere in the runtime
1122
1183
  if (state === UnreferencedState.SweepReady) {