@fluidframework/datastore 2.0.0-rc.1.0.3 → 2.0.0-rc.1.0.5

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.
@@ -97,6 +97,7 @@ export function createChannelServiceEndpoints(
97
97
  };
98
98
  }
99
99
 
100
+ /** Used to get the channel's summary for the DDS or DataStore attach op */
100
101
  export function summarizeChannel(
101
102
  channel: IChannel,
102
103
  fullTree: boolean = false,
@@ -52,6 +52,7 @@ import {
52
52
  VisibilityState,
53
53
  ITelemetryContext,
54
54
  IIdCompressor,
55
+ gcDataBlobKey,
55
56
  } from "@fluidframework/runtime-definitions";
56
57
  import {
57
58
  convertSnapshotTreeToSummaryTree,
@@ -64,6 +65,8 @@ import {
64
65
  exceptionToResponse,
65
66
  GCDataBuilder,
66
67
  unpackChildNodesUsedRoutes,
68
+ addBlobToSummary,
69
+ processAttachMessageGCData,
67
70
  } from "@fluidframework/runtime-utils";
68
71
  import {
69
72
  IChannel,
@@ -482,7 +485,7 @@ export class FluidDataStoreRuntime
482
485
  this.notBoundedChannelContextSet.delete(channel.id);
483
486
  // If our data store is attached, then attach the channel.
484
487
  if (this.isAttached) {
485
- this.attachChannel(channel);
488
+ this.makeChannelLocallyVisible(channel);
486
489
  return;
487
490
  }
488
491
 
@@ -610,6 +613,13 @@ export class FluidDataStoreRuntime
610
613
  const attachMessage = message.contents as IAttachMessage;
611
614
  const id = attachMessage.id;
612
615
 
616
+ // We need to process the GC Data for both local and remote attach messages
617
+ processAttachMessageGCData(attachMessage.snapshot, (nodeId, toPath) => {
618
+ // Note: nodeId will be "/" unless and until we support sub-DDS GC Nodes
619
+ const fromPath = `/${this.id}/${id}${nodeId === "/" ? "" : nodeId}`;
620
+ this.dataStoreContext.addedGCOutboundRoute?.(fromPath, toPath);
621
+ });
622
+
613
623
  // If a non-local operation then go and create the object
614
624
  // Otherwise mark it as officially attached.
615
625
  if (local) {
@@ -812,6 +822,64 @@ export class FluidDataStoreRuntime
812
822
  }
813
823
 
814
824
  public getAttachSummary(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats {
825
+ const summaryBuilder = new SummaryTreeBuilder();
826
+ this.visitLocalBoundContextsDuringAttach(
827
+ (contextId: string, context: LocalChannelContextBase) => {
828
+ let summaryTree: ISummaryTreeWithStats;
829
+ if (context.isLoaded) {
830
+ const contextSummary = context.getAttachSummary(telemetryContext);
831
+ assert(
832
+ contextSummary.summary.type === SummaryType.Tree,
833
+ 0x180 /* "getAttachSummary should always return a tree" */,
834
+ );
835
+
836
+ summaryTree = { stats: contextSummary.stats, summary: contextSummary.summary };
837
+ } else {
838
+ // If this channel is not yet loaded, then there should be no changes in the snapshot from which
839
+ // it was created as it is detached container. So just use the previous snapshot.
840
+ assert(
841
+ !!this.dataStoreContext.baseSnapshot,
842
+ 0x181 /* "BaseSnapshot should be there as detached container loaded from snapshot" */,
843
+ );
844
+ summaryTree = convertSnapshotTreeToSummaryTree(
845
+ this.dataStoreContext.baseSnapshot.trees[contextId],
846
+ );
847
+ }
848
+ summaryBuilder.addWithStats(contextId, summaryTree);
849
+ },
850
+ );
851
+
852
+ return summaryBuilder.getSummaryTree();
853
+ }
854
+
855
+ /**
856
+ * Get the GC Data for the initial state being attached so remote clients can learn of this DataStore's outbound routes
857
+ */
858
+ public getAttachGCData(telemetryContext?: ITelemetryContext): IGarbageCollectionData {
859
+ const gcDataBuilder = new GCDataBuilder();
860
+ this.visitLocalBoundContextsDuringAttach(
861
+ (contextId: string, context: LocalChannelContextBase) => {
862
+ if (context.isLoaded) {
863
+ const contextGCData = context.getAttachGCData(telemetryContext);
864
+
865
+ // Incorporate the GC Data for this context
866
+ gcDataBuilder.prefixAndAddNodes(contextId, contextGCData.gcNodes);
867
+ }
868
+ // else: Rehydrating detached container case. GC doesn't run until the container is attached, so nothing to do here.
869
+ },
870
+ );
871
+ this.updateGCNodes(gcDataBuilder);
872
+
873
+ return gcDataBuilder.getGCData();
874
+ }
875
+
876
+ /**
877
+ * Helper method for preparing to attach this dataStore.
878
+ * Runs the callback for each bound context to incorporate its data however the caller specifies
879
+ */
880
+ private visitLocalBoundContextsDuringAttach(
881
+ visitor: (contextId: string, context: LocalChannelContextBase) => void,
882
+ ): void {
815
883
  /**
816
884
  * back-compat 0.59.1000 - getAttachSummary() is called when making a data store globally visible (previously
817
885
  * attaching state). Ideally, attachGraph() should have already be called making it locally visible. However,
@@ -832,39 +900,15 @@ export class FluidDataStoreRuntime
832
900
  // "The data store should be locally visible when generating attach summary",
833
901
  // );
834
902
 
835
- const summaryBuilder = new SummaryTreeBuilder();
836
-
837
- // Craft the .attributes file for each shared object
838
903
  for (const [contextId, context] of this.contexts) {
839
904
  if (!(context instanceof LocalChannelContextBase)) {
840
905
  throw new LoggingError("Should only be called with local channel handles");
841
906
  }
842
907
 
843
908
  if (!this.notBoundedChannelContextSet.has(contextId)) {
844
- let summaryTree: ISummaryTreeWithStats;
845
- if (context.isLoaded) {
846
- const contextSummary = context.getAttachSummary(telemetryContext);
847
- assert(
848
- contextSummary.summary.type === SummaryType.Tree,
849
- 0x180 /* "getAttachSummary should always return a tree" */,
850
- );
851
- summaryTree = { stats: contextSummary.stats, summary: contextSummary.summary };
852
- } else {
853
- // If this channel is not yet loaded, then there should be no changes in the snapshot from which
854
- // it was created as it is detached container. So just use the previous snapshot.
855
- assert(
856
- !!this.dataStoreContext.baseSnapshot,
857
- 0x181 /* "BaseSnapshot should be there as detached container loaded from snapshot" */,
858
- );
859
- summaryTree = convertSnapshotTreeToSummaryTree(
860
- this.dataStoreContext.baseSnapshot.trees[contextId],
861
- );
862
- }
863
- summaryBuilder.addWithStats(contextId, summaryTree);
909
+ visitor(contextId, context);
864
910
  }
865
911
  }
866
-
867
- return summaryBuilder.getSummaryTree();
868
912
  }
869
913
 
870
914
  public submitMessage(type: DataStoreMessageType, content: any, localOpMetadata: unknown) {
@@ -890,9 +934,10 @@ export class FluidDataStoreRuntime
890
934
  }
891
935
 
892
936
  /**
893
- * Attach channel should only be called after the data store has been attached
937
+ * Assuming this DataStore is already attached, this will make the given channel locally visible
938
+ * by submitting its attach op.
894
939
  */
895
- private attachChannel(channel: IChannel): void {
940
+ private makeChannelLocallyVisible(channel: IChannel): void {
896
941
  this.verifyNotClosed();
897
942
  // If this handle is already attached no need to attach again.
898
943
  if (channel.handle.isAttached) {
@@ -912,6 +957,11 @@ export class FluidDataStoreRuntime
912
957
  true /* fullTree */,
913
958
  false /* trackState */,
914
959
  );
960
+
961
+ // We need to include the channel's GC Data so remote clients can learn of this channel's outbound routes
962
+ const gcData = channel.getGCData(/* fullGC: */ true);
963
+ addBlobToSummary(summarizeResult, gcDataBlobKey, JSON.stringify(gcData));
964
+
915
965
  // Attach message needs the summary in ITree format. Convert the ISummaryTree into an ITree.
916
966
  const snapshot = convertSummaryTreeToITree(summarizeResult.summary);
917
967
 
@@ -125,6 +125,11 @@ export abstract class LocalChannelContextBase implements IChannelContext {
125
125
  return summarizeChannelAsync(channel, fullTree, trackState, telemetryContext);
126
126
  }
127
127
 
128
+ /**
129
+ * For crafting the DataStore attach op. Only to be called when the channel is loaded (if applicable).
130
+ *
131
+ * Synchronously generates the channel's attach summary to be joined with the same from the DataStore's other channels
132
+ */
128
133
  public getAttachSummary(telemetryContext?: ITelemetryContext): ISummarizeResult {
129
134
  assert(
130
135
  this._channel !== undefined,
@@ -138,6 +143,19 @@ export abstract class LocalChannelContextBase implements IChannelContext {
138
143
  );
139
144
  }
140
145
 
146
+ /**
147
+ * For crafting the DataStore attach op. Only to be called when the channel is loaded (if applicable).
148
+ *
149
+ * Synchronously generates the channel's attach GC data (set of outbound routes in the initial state)
150
+ * to be joined with the same from the DataStore's other channels
151
+ */
152
+ public getAttachGCData(telemetryContext?: ITelemetryContext): IGarbageCollectionData {
153
+ assert(this._channel !== undefined, "Local Channel should be loaded before being attached");
154
+
155
+ // We need the GC Data to detect references added in this attach op
156
+ return this._channel.getGCData(/* fullGC: */ true);
157
+ }
158
+
141
159
  public makeVisible(): void {
142
160
  if (this.globallyVisible) {
143
161
  throw new Error("Channel is already globally visible");