@fluidframework/container-runtime 2.0.0-internal.2.3.1 → 2.0.0-internal.3.0.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 (79) hide show
  1. package/dist/blobManager.d.ts +3 -1
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +35 -2
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +45 -42
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +89 -40
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +1 -0
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +7 -2
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/garbageCollection.d.ts +15 -7
  14. package/dist/garbageCollection.d.ts.map +1 -1
  15. package/dist/garbageCollection.js +96 -36
  16. package/dist/garbageCollection.js.map +1 -1
  17. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  18. package/dist/opLifecycle/outbox.js +0 -1
  19. package/dist/opLifecycle/outbox.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/pendingStateManager.d.ts +4 -13
  24. package/dist/pendingStateManager.d.ts.map +1 -1
  25. package/dist/pendingStateManager.js +130 -160
  26. package/dist/pendingStateManager.js.map +1 -1
  27. package/dist/summarizer.js.map +1 -1
  28. package/dist/summarizerClientElection.d.ts +1 -2
  29. package/dist/summarizerClientElection.d.ts.map +1 -1
  30. package/dist/summarizerClientElection.js +3 -30
  31. package/dist/summarizerClientElection.js.map +1 -1
  32. package/dist/summarizerTypes.d.ts +0 -4
  33. package/dist/summarizerTypes.d.ts.map +1 -1
  34. package/dist/summarizerTypes.js.map +1 -1
  35. package/lib/blobManager.d.ts +3 -1
  36. package/lib/blobManager.d.ts.map +1 -1
  37. package/lib/blobManager.js +35 -2
  38. package/lib/blobManager.js.map +1 -1
  39. package/lib/containerRuntime.d.ts +45 -42
  40. package/lib/containerRuntime.d.ts.map +1 -1
  41. package/lib/containerRuntime.js +89 -40
  42. package/lib/containerRuntime.js.map +1 -1
  43. package/lib/dataStoreContext.d.ts +1 -0
  44. package/lib/dataStoreContext.d.ts.map +1 -1
  45. package/lib/dataStoreContext.js +7 -2
  46. package/lib/dataStoreContext.js.map +1 -1
  47. package/lib/garbageCollection.d.ts +15 -7
  48. package/lib/garbageCollection.d.ts.map +1 -1
  49. package/lib/garbageCollection.js +97 -37
  50. package/lib/garbageCollection.js.map +1 -1
  51. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  52. package/lib/opLifecycle/outbox.js +0 -1
  53. package/lib/opLifecycle/outbox.js.map +1 -1
  54. package/lib/packageVersion.d.ts +1 -1
  55. package/lib/packageVersion.js +1 -1
  56. package/lib/packageVersion.js.map +1 -1
  57. package/lib/pendingStateManager.d.ts +4 -13
  58. package/lib/pendingStateManager.d.ts.map +1 -1
  59. package/lib/pendingStateManager.js +130 -160
  60. package/lib/pendingStateManager.js.map +1 -1
  61. package/lib/summarizer.js.map +1 -1
  62. package/lib/summarizerClientElection.d.ts +1 -2
  63. package/lib/summarizerClientElection.d.ts.map +1 -1
  64. package/lib/summarizerClientElection.js +3 -30
  65. package/lib/summarizerClientElection.js.map +1 -1
  66. package/lib/summarizerTypes.d.ts +0 -4
  67. package/lib/summarizerTypes.d.ts.map +1 -1
  68. package/lib/summarizerTypes.js.map +1 -1
  69. package/package.json +55 -20
  70. package/src/blobManager.ts +41 -2
  71. package/src/containerRuntime.ts +118 -85
  72. package/src/dataStoreContext.ts +12 -6
  73. package/src/garbageCollection.ts +103 -34
  74. package/src/opLifecycle/outbox.ts +0 -2
  75. package/src/packageVersion.ts +1 -1
  76. package/src/pendingStateManager.ts +146 -187
  77. package/src/summarizer.ts +1 -1
  78. package/src/summarizerClientElection.ts +1 -30
  79. package/src/summarizerTypes.ts +0 -4
@@ -145,7 +145,10 @@ class GarbageCollector {
145
145
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
146
146
  // outbound routes from that node.
147
147
  this.newReferencesSinceLastRun = new Map();
148
+ // A list of nodes that have been tombstoned.
148
149
  this.tombstones = [];
150
+ // A list of nodes that have been deleted during sweep phase.
151
+ this.deletedNodes = new Set();
149
152
  // Map of node ids to their unreferenced state tracker.
150
153
  this.unreferencedNodesState = new Map();
151
154
  // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
@@ -214,8 +217,7 @@ class GarbageCollector {
214
217
  // flag in GC options to false.
215
218
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
216
219
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
217
- // ...unless we're using the TestOverride
218
- this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
220
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
219
221
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
220
222
  if (this.mc.config.getBoolean(garbageCollectionConstants_1.runSessionExpiryKey) && this.gcEnabled) {
221
223
  this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : garbageCollectionConstants_1.defaultSessionExpiryDurationMs;
@@ -271,8 +273,9 @@ class GarbageCollector {
271
273
  }
272
274
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
273
275
  this.testMode = (_h = this.mc.config.getBoolean(garbageCollectionConstants_1.gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
274
- // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
275
- this.tombstoneMode = this.mc.config.getBoolean(garbageCollectionConstants_1.disableTombstoneKey) !== true;
276
+ // Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
277
+ // via feature flags.
278
+ this.tombstoneMode = !this.shouldRunSweep && this.mc.config.getBoolean(garbageCollectionConstants_1.disableTombstoneKey) !== true;
276
279
  // If GC ran in the container that generated the base snapshot, it will have a GC tree.
277
280
  this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[runtime_definitions_1.gcTreeKey]) !== undefined;
278
281
  // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
@@ -322,7 +325,9 @@ class GarbageCollector {
322
325
  }
323
326
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
324
327
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
325
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
328
+ return Object.keys(gcState.gcNodes).length === 1
329
+ ? undefined
330
+ : { gcState, tombstones: undefined, deletedNodes: undefined };
326
331
  }
327
332
  catch (error) {
328
333
  const dpe = container_utils_1.DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
@@ -435,24 +440,31 @@ class GarbageCollector {
435
440
  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);
436
441
  }
437
442
  /**
438
- * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
439
- * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
440
- * in use or not.
443
+ * Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
444
+ * during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
441
445
  */
442
446
  async initializeBaseState() {
443
447
  const baseSnapshotData = await this.baseSnapshotDataP;
444
448
  /**
445
- * The base snapshot data or tombstone state will not be present if the container is loaded from:
449
+ * The base snapshot data will not be present if the container is loaded from:
446
450
  * 1. The first summary created by the detached container.
447
451
  * 2. A summary that was generated with GC disabled.
448
452
  * 3. A summary that was generated before GC even existed.
449
- * 4. A summary that was generated with tombstone feature disabled.
450
453
  */
451
- if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
454
+ if (baseSnapshotData === undefined) {
452
455
  return;
453
456
  }
454
- this.tombstones = Array.from(baseSnapshotData.tombstones);
455
- this.runtime.updateTombstonedRoutes(this.tombstones);
457
+ // Initialize the deleted nodes from the snapshot. This is done irrespective of whether sweep is enabled or not
458
+ // to identify deleted nodes' usage.
459
+ if (baseSnapshotData.deletedNodes !== undefined) {
460
+ this.deletedNodes = new Set(baseSnapshotData.deletedNodes);
461
+ }
462
+ // If running in tombstone mode, initialize the tombstone state from the snapshot. Also, notify the runtime of
463
+ // tombstone routes.
464
+ if (this.tombstoneMode && baseSnapshotData.tombstones !== undefined) {
465
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
466
+ this.runtime.updateTombstonedRoutes(this.tombstones);
467
+ }
456
468
  }
457
469
  /**
458
470
  * Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
@@ -479,9 +491,30 @@ class GarbageCollector {
479
491
  }
480
492
  ;
481
493
  this.unreferencedNodesState.clear();
482
- // If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
483
- // container as per the state in the snapshot data.
484
- if (this.tombstoneMode) {
494
+ // If running sweep, the tombstone state represents the list of nodes that have been deleted during sweep.
495
+ // If running in tombstone mode, the tombstone state represents the list of nodes that have been marked as
496
+ // tombstones.
497
+ // If this call is because we are refreshing from a snapshot due to an ack, it is likely that the GC state
498
+ // in the snapshot is newer than this client's. And so, the deleted / tombstone nodes need to be updated.
499
+ if (this.shouldRunSweep) {
500
+ const snapshotDeletedNodes = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? new Set(snapshotData.tombstones) : undefined;
501
+ // If the snapshot contains deleted nodes that are not yet deleted by this client, ask the runtime to
502
+ // delete them.
503
+ if (snapshotDeletedNodes !== undefined) {
504
+ const newDeletedNodes = [];
505
+ for (const nodeId of snapshotDeletedNodes) {
506
+ if (!this.deletedNodes.has(nodeId)) {
507
+ newDeletedNodes.push(nodeId);
508
+ }
509
+ }
510
+ if (newDeletedNodes.length > 0) {
511
+ // Call container runtime to delete these nodes and add deleted nodes to this.deletedNodes.
512
+ }
513
+ }
514
+ }
515
+ else if (this.tombstoneMode) {
516
+ // The snapshot may contain more or fewer tombstone nodes than this client. Update tombstone state and
517
+ // notify the runtime to update its state as well.
485
518
  this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
486
519
  this.runtime.updateTombstonedRoutes(this.tombstones);
487
520
  }
@@ -506,6 +539,7 @@ class GarbageCollector {
506
539
  this.latestSummaryData = {
507
540
  serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
508
541
  serializedTombstones: JSON.stringify(snapshotData.tombstones),
542
+ serializedDeletedNodes: JSON.stringify(snapshotData.deletedNodes),
509
543
  };
510
544
  }
511
545
  }
@@ -625,17 +659,25 @@ class GarbageCollector {
625
659
  };
626
660
  }
627
661
  const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
662
+ // Serialize and write deleted nodes, if any. This is done irrespective of whether sweep is enabled or not so
663
+ // to identify deleted nodes' usage.
664
+ const serializedDeletedNodes = this.deletedNodes.size > 0
665
+ ? JSON.stringify(Array.from(this.deletedNodes).sort())
666
+ : undefined;
667
+ // If running in tombstone mode, serialize and write tombstones, if any.
628
668
  const serializedTombstones = this.tombstoneMode
629
669
  ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
630
670
  : undefined;
631
671
  /**
632
- * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
633
- * summary, send summary handles for them. Otherwise, send the data in summary blobs.
672
+ * Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
673
+ * write summary handle instead of summary tree for GC.
674
+ * Otherwise, write the GC summary tree. In the tree, for each of these that changed, write a summary blob and
675
+ * for each of these that did not change, write a summary handle.
634
676
  */
635
677
  if (this.trackGCState) {
636
- this.pendingSummaryData = { serializedGCState, serializedTombstones };
678
+ this.pendingSummaryData = { serializedGCState, serializedTombstones, serializedDeletedNodes };
637
679
  if (trackState && !fullTree && this.latestSummaryData !== undefined) {
638
- // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
680
+ // If nothing changed since last summary, send a summary handle for the entire GC data.
639
681
  if (this.latestSummaryData.serializedGCState === serializedGCState
640
682
  && this.latestSummaryData.serializedTombstones === serializedTombstones) {
641
683
  const stats = (0, runtime_utils_1.mergeStats)();
@@ -649,24 +691,25 @@ class GarbageCollector {
649
691
  stats,
650
692
  };
651
693
  }
652
- // If either or both of GC state or tombstone state changed, build a GC summary tree.
653
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
694
+ // If some state changed, build a GC summary tree.
695
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
654
696
  }
655
697
  }
656
698
  // If not tracking GC state, build a GC summary tree without any summary handles.
657
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
699
+ return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
658
700
  }
659
701
  /**
660
- * Builds the GC summary tree which contains GC state and tombstone state.
661
- * If trackState is false, both GC state and tombstone state are written as summary blobs.
662
- * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
702
+ * Builds the GC summary tree which contains GC state, deleted nodes and tombstones.
703
+ * If trackState is false, all of GC state, deleted nodes and tombstones are written as summary blobs.
704
+ * If trackState is true, only states that changed are written. Rest are written as handles.
663
705
  * @param serializedGCState - The GC state serialized as string.
664
- * @param serializedTombstones - THe tombstone state serialized as string.
706
+ * @param serializedTombstones - The tombstone state serialized as string.
707
+ * @param serializedDeletedNodes - Deleted nodes serialized as string.
665
708
  * @param trackState - Whether we are tracking GC state across summaries.
666
709
  * @returns the GC summary tree.
667
710
  */
668
- buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
669
- var _a, _b;
711
+ buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
712
+ var _a, _b, _c;
670
713
  const gcStateBlobKey = `${runtime_definitions_1.gcBlobPrefix}_root`;
671
714
  const builder = new runtime_utils_1.SummaryTreeBuilder();
672
715
  // If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
@@ -676,16 +719,26 @@ class GarbageCollector {
676
719
  else {
677
720
  builder.addBlob(gcStateBlobKey, serializedGCState);
678
721
  }
679
- // If there is no tombstone data, return only the GC state.
680
- if (serializedTombstones === undefined) {
722
+ // If tombstones exist, write a summary handle if it hasn't changed. If it has changed, write a
723
+ // summary blob.
724
+ if (serializedTombstones !== undefined) {
725
+ if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
726
+ builder.addHandle(runtime_definitions_1.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${runtime_definitions_1.gcTreeKey}/${runtime_definitions_1.gcTombstoneBlobKey}`);
727
+ }
728
+ else {
729
+ builder.addBlob(runtime_definitions_1.gcTombstoneBlobKey, serializedTombstones);
730
+ }
731
+ }
732
+ // If there are no deleted nodes, return the summary tree.
733
+ if (serializedDeletedNodes === undefined) {
681
734
  return builder.getSummaryTree();
682
735
  }
683
- // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
684
- if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
685
- builder.addHandle(runtime_definitions_1.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${runtime_definitions_1.gcTreeKey}/${runtime_definitions_1.gcTombstoneBlobKey}`);
736
+ // If the deleted nodes hasn't changed, write a summary handle, else write a summary blob for it.
737
+ if (((_c = this.latestSummaryData) === null || _c === void 0 ? void 0 : _c.serializedDeletedNodes) === serializedDeletedNodes && trackState) {
738
+ builder.addHandle(runtime_definitions_1.gcDeletedBlobKey, protocol_definitions_1.SummaryType.Blob, `/${runtime_definitions_1.gcTreeKey}/${runtime_definitions_1.gcDeletedBlobKey}`);
686
739
  }
687
740
  else {
688
- builder.addBlob(runtime_definitions_1.gcTombstoneBlobKey, serializedTombstones);
741
+ builder.addBlob(runtime_definitions_1.gcDeletedBlobKey, serializedDeletedNodes);
689
742
  }
690
743
  return builder.getSummaryTree();
691
744
  }
@@ -810,6 +863,13 @@ class GarbageCollector {
810
863
  }, undefined /* packagePath */);
811
864
  }
812
865
  }
866
+ /**
867
+ * Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
868
+ * cases where objects are used after they are deleted and throw / log errors accordingly.
869
+ */
870
+ isNodeDeleted(nodePath) {
871
+ return this.deletedNodes.has(nodePath);
872
+ }
813
873
  dispose() {
814
874
  var _a;
815
875
  (_a = this.sessionExpiryTimer) === null || _a === void 0 ? void 0 : _a.clear();
@@ -1121,7 +1181,7 @@ class GarbageCollector {
1121
1181
  this.mc.logger.sendErrorEvent(event);
1122
1182
  }
1123
1183
  }
1124
- // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
1184
+ // If SweepReady Usage Detection is enabled, the handler may close the interactive container.
1125
1185
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
1126
1186
  // and errors will arise elsewhere in the runtime
1127
1187
  if (state === exports.UnreferencedState.SweepReady) {