@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
@@ -30,6 +30,7 @@ import {
30
30
  IGarbageCollectionNodeData,
31
31
  IGarbageCollectionSummaryDetailsLegacy,
32
32
  ISummaryTreeWithStats,
33
+ gcDeletedBlobKey,
33
34
  } from "@fluidframework/runtime-definitions";
34
35
  import {
35
36
  mergeStats,
@@ -172,6 +173,8 @@ export interface IGarbageCollector {
172
173
  ): void;
173
174
  /** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
174
175
  addedOutboundReference(fromNodePath: string, toNodePath: string): void;
176
+ /** Returns true if this node has been deleted by GC during sweep phase. */
177
+ isNodeDeleted(nodePath: string): boolean;
175
178
  setConnectionState(connected: boolean, clientId?: string): void;
176
179
  dispose(): void;
177
180
  }
@@ -226,6 +229,7 @@ interface IUnreferencedEventProps {
226
229
  interface IGCSummaryTrackingData {
227
230
  serializedGCState: string | undefined;
228
231
  serializedTombstones: string | undefined;
232
+ serializedDeletedNodes: string | undefined;
229
233
  }
230
234
 
231
235
  /**
@@ -419,7 +423,10 @@ export class GarbageCollector implements IGarbageCollector {
419
423
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
420
424
  // outbound routes from that node.
421
425
  private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
426
+ // A list of nodes that have been tombstoned.
422
427
  private tombstones: string[] = [];
428
+ // A list of nodes that have been deleted during sweep phase.
429
+ private deletedNodes: Set<string> = new Set();
423
430
 
424
431
  /**
425
432
  * Keeps track of the GC data from the latest summary successfully submitted to and acked from the server.
@@ -565,8 +572,7 @@ export class GarbageCollector implements IGarbageCollector {
565
572
  // flag in GC options to false.
566
573
  this.gcEnabled = this.gcOptions.gcAllowed !== false;
567
574
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
568
- // ...unless we're using the TestOverride
569
- this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
575
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
570
576
 
571
577
  // Set the Session Expiry only if the flag is enabled and GC is enabled.
572
578
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
@@ -640,8 +646,9 @@ export class GarbageCollector implements IGarbageCollector {
640
646
 
641
647
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
642
648
  this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? this.gcOptions.runGCInTestMode === true;
643
- // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
644
- this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
649
+ // Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
650
+ // via feature flags.
651
+ this.tombstoneMode = !this.shouldRunSweep && this.mc.config.getBoolean(disableTombstoneKey) !== true;
645
652
 
646
653
  // If GC ran in the container that generated the base snapshot, it will have a GC tree.
647
654
  this.wasGCRunInLatestSummary = baseSnapshot?.trees[gcTreeKey] !== undefined;
@@ -704,7 +711,9 @@ export class GarbageCollector implements IGarbageCollector {
704
711
  }
705
712
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
706
713
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
707
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
714
+ return Object.keys(gcState.gcNodes).length === 1
715
+ ? undefined
716
+ : { gcState, tombstones: undefined, deletedNodes: undefined };
708
717
  } catch (error) {
709
718
  const dpe = DataProcessingError.wrapIfUnrecognized(
710
719
  error,
@@ -782,24 +791,33 @@ export class GarbageCollector implements IGarbageCollector {
782
791
  }
783
792
 
784
793
  /**
785
- * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
786
- * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
787
- * in use or not.
794
+ * Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
795
+ * during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
788
796
  */
789
797
  public async initializeBaseState(): Promise<void> {
790
798
  const baseSnapshotData = await this.baseSnapshotDataP;
791
799
  /**
792
- * The base snapshot data or tombstone state will not be present if the container is loaded from:
800
+ * The base snapshot data will not be present if the container is loaded from:
793
801
  * 1. The first summary created by the detached container.
794
802
  * 2. A summary that was generated with GC disabled.
795
803
  * 3. A summary that was generated before GC even existed.
796
- * 4. A summary that was generated with tombstone feature disabled.
797
804
  */
798
- if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
805
+ if (baseSnapshotData === undefined) {
799
806
  return;
800
807
  }
801
- this.tombstones = Array.from(baseSnapshotData.tombstones);
802
- this.runtime.updateTombstonedRoutes(this.tombstones);
808
+
809
+ // Initialize the deleted nodes from the snapshot. This is done irrespective of whether sweep is enabled or not
810
+ // to identify deleted nodes' usage.
811
+ if (baseSnapshotData.deletedNodes !== undefined) {
812
+ this.deletedNodes = new Set(baseSnapshotData.deletedNodes);
813
+ }
814
+
815
+ // If running in tombstone mode, initialize the tombstone state from the snapshot. Also, notify the runtime of
816
+ // tombstone routes.
817
+ if (this.tombstoneMode && baseSnapshotData.tombstones !== undefined) {
818
+ this.tombstones = Array.from(baseSnapshotData.tombstones);
819
+ this.runtime.updateTombstonedRoutes(this.tombstones);
820
+ }
803
821
  }
804
822
 
805
823
  /**
@@ -831,9 +849,29 @@ export class GarbageCollector implements IGarbageCollector {
831
849
  };
832
850
  this.unreferencedNodesState.clear();
833
851
 
834
- // If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
835
- // container as per the state in the snapshot data.
836
- if (this.tombstoneMode) {
852
+ // If running sweep, the tombstone state represents the list of nodes that have been deleted during sweep.
853
+ // If running in tombstone mode, the tombstone state represents the list of nodes that have been marked as
854
+ // tombstones.
855
+ // If this call is because we are refreshing from a snapshot due to an ack, it is likely that the GC state
856
+ // in the snapshot is newer than this client's. And so, the deleted / tombstone nodes need to be updated.
857
+ if (this.shouldRunSweep) {
858
+ const snapshotDeletedNodes = snapshotData?.tombstones ? new Set(snapshotData.tombstones) : undefined;
859
+ // If the snapshot contains deleted nodes that are not yet deleted by this client, ask the runtime to
860
+ // delete them.
861
+ if (snapshotDeletedNodes !== undefined) {
862
+ const newDeletedNodes: string[] = [];
863
+ for (const nodeId of snapshotDeletedNodes) {
864
+ if (!this.deletedNodes.has(nodeId)) {
865
+ newDeletedNodes.push(nodeId);
866
+ }
867
+ }
868
+ if (newDeletedNodes.length > 0) {
869
+ // Call container runtime to delete these nodes and add deleted nodes to this.deletedNodes.
870
+ }
871
+ }
872
+ } else if (this.tombstoneMode) {
873
+ // The snapshot may contain more or fewer tombstone nodes than this client. Update tombstone state and
874
+ // notify the runtime to update its state as well.
837
875
  this.tombstones = snapshotData?.tombstones ? Array.from(snapshotData.tombstones) : [];
838
876
  this.runtime.updateTombstonedRoutes(this.tombstones);
839
877
  }
@@ -869,6 +907,7 @@ export class GarbageCollector implements IGarbageCollector {
869
907
  this.latestSummaryData = {
870
908
  serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
871
909
  serializedTombstones: JSON.stringify(snapshotData.tombstones),
910
+ serializedDeletedNodes: JSON.stringify(snapshotData.deletedNodes),
872
911
  };
873
912
  }
874
913
  }
@@ -1020,18 +1059,26 @@ export class GarbageCollector implements IGarbageCollector {
1020
1059
  }
1021
1060
 
1022
1061
  const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
1062
+ // Serialize and write deleted nodes, if any. This is done irrespective of whether sweep is enabled or not so
1063
+ // to identify deleted nodes' usage.
1064
+ const serializedDeletedNodes = this.deletedNodes.size > 0
1065
+ ? JSON.stringify(Array.from(this.deletedNodes).sort())
1066
+ : undefined;
1067
+ // If running in tombstone mode, serialize and write tombstones, if any.
1023
1068
  const serializedTombstones = this.tombstoneMode
1024
1069
  ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
1025
1070
  : undefined;
1026
1071
 
1027
1072
  /**
1028
- * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
1029
- * summary, send summary handles for them. Otherwise, send the data in summary blobs.
1073
+ * Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
1074
+ * write summary handle instead of summary tree for GC.
1075
+ * Otherwise, write the GC summary tree. In the tree, for each of these that changed, write a summary blob and
1076
+ * for each of these that did not change, write a summary handle.
1030
1077
  */
1031
1078
  if (this.trackGCState) {
1032
- this.pendingSummaryData = { serializedGCState, serializedTombstones };
1079
+ this.pendingSummaryData = { serializedGCState, serializedTombstones, serializedDeletedNodes };
1033
1080
  if (trackState && !fullTree && this.latestSummaryData !== undefined) {
1034
- // If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
1081
+ // If nothing changed since last summary, send a summary handle for the entire GC data.
1035
1082
  if (this.latestSummaryData.serializedGCState === serializedGCState
1036
1083
  && this.latestSummaryData.serializedTombstones === serializedTombstones) {
1037
1084
  const stats = mergeStats();
@@ -1046,26 +1093,30 @@ export class GarbageCollector implements IGarbageCollector {
1046
1093
  };
1047
1094
  }
1048
1095
 
1049
- // If either or both of GC state or tombstone state changed, build a GC summary tree.
1050
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
1096
+ // If some state changed, build a GC summary tree.
1097
+ return this.buildGCSummaryTree(
1098
+ serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
1051
1099
  }
1052
1100
  }
1053
1101
  // If not tracking GC state, build a GC summary tree without any summary handles.
1054
- return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
1102
+ return this.buildGCSummaryTree(
1103
+ serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
1055
1104
  }
1056
1105
 
1057
1106
  /**
1058
- * Builds the GC summary tree which contains GC state and tombstone state.
1059
- * If trackState is false, both GC state and tombstone state are written as summary blobs.
1060
- * If trackState is true, summary blob is written for GC state or tombstone state if they changed.
1107
+ * Builds the GC summary tree which contains GC state, deleted nodes and tombstones.
1108
+ * If trackState is false, all of GC state, deleted nodes and tombstones are written as summary blobs.
1109
+ * If trackState is true, only states that changed are written. Rest are written as handles.
1061
1110
  * @param serializedGCState - The GC state serialized as string.
1062
- * @param serializedTombstones - THe tombstone state serialized as string.
1111
+ * @param serializedTombstones - The tombstone state serialized as string.
1112
+ * @param serializedDeletedNodes - Deleted nodes serialized as string.
1063
1113
  * @param trackState - Whether we are tracking GC state across summaries.
1064
1114
  * @returns the GC summary tree.
1065
1115
  */
1066
1116
  private buildGCSummaryTree(
1067
1117
  serializedGCState: string,
1068
1118
  serializedTombstones: string | undefined,
1119
+ serializedDeletedNodes: string | undefined,
1069
1120
  trackState: boolean,
1070
1121
  ): ISummaryTreeWithStats {
1071
1122
  const gcStateBlobKey = `${gcBlobPrefix}_root`;
@@ -1078,16 +1129,26 @@ export class GarbageCollector implements IGarbageCollector {
1078
1129
  builder.addBlob(gcStateBlobKey, serializedGCState);
1079
1130
  }
1080
1131
 
1081
- // If there is no tombstone data, return only the GC state.
1082
- if (serializedTombstones === undefined) {
1132
+ // If tombstones exist, write a summary handle if it hasn't changed. If it has changed, write a
1133
+ // summary blob.
1134
+ if (serializedTombstones !== undefined) {
1135
+ if (this.latestSummaryData?.serializedTombstones === serializedTombstones && trackState) {
1136
+ builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
1137
+ } else {
1138
+ builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
1139
+ }
1140
+ }
1141
+
1142
+ // If there are no deleted nodes, return the summary tree.
1143
+ if (serializedDeletedNodes === undefined) {
1083
1144
  return builder.getSummaryTree();
1084
1145
  }
1085
1146
 
1086
- // If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
1087
- if (this.latestSummaryData?.serializedTombstones === serializedTombstones && trackState) {
1088
- builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
1147
+ // If the deleted nodes hasn't changed, write a summary handle, else write a summary blob for it.
1148
+ if (this.latestSummaryData?.serializedDeletedNodes === serializedDeletedNodes && trackState) {
1149
+ builder.addHandle(gcDeletedBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcDeletedBlobKey}`);
1089
1150
  } else {
1090
- builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
1151
+ builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
1091
1152
  }
1092
1153
  return builder.getSummaryTree();
1093
1154
  }
@@ -1257,6 +1318,14 @@ export class GarbageCollector implements IGarbageCollector {
1257
1318
  }
1258
1319
  }
1259
1320
 
1321
+ /**
1322
+ * Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
1323
+ * cases where objects are used after they are deleted and throw / log errors accordingly.
1324
+ */
1325
+ public isNodeDeleted(nodePath: string): boolean {
1326
+ return this.deletedNodes.has(nodePath);
1327
+ }
1328
+
1260
1329
  public dispose(): void {
1261
1330
  this.sessionExpiryTimer?.clear();
1262
1331
  this.sessionExpiryTimer = undefined;
@@ -1640,7 +1709,7 @@ export class GarbageCollector implements IGarbageCollector {
1640
1709
  }
1641
1710
  }
1642
1711
 
1643
- // If SweepReady Usage Detection is enabed, the handler may close the interactive container.
1712
+ // If SweepReady Usage Detection is enabled, the handler may close the interactive container.
1644
1713
  // Once Sweep is fully implemented, this will be removed since the objects will be gone
1645
1714
  // and errors will arise elsewhere in the runtime
1646
1715
  if (state === UnreferencedState.SweepReady) {
@@ -207,8 +207,6 @@ export class Outbox {
207
207
 
208
208
  clientSequenceNumber++;
209
209
  }
210
-
211
- this.params.pendingStateManager.onFlush();
212
210
  }
213
211
 
214
212
  public checkpoint() {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.2.3.1";
9
+ export const pkgVersion = "2.0.0-internal.3.0.0";