@fluidframework/container-runtime 2.0.0-internal.3.0.1 → 2.0.0-internal.3.0.3

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 (74) hide show
  1. package/dist/containerRuntime.d.ts +1 -1
  2. package/dist/containerRuntime.d.ts.map +1 -1
  3. package/dist/containerRuntime.js +58 -34
  4. package/dist/containerRuntime.js.map +1 -1
  5. package/dist/dataStoreContext.d.ts +12 -0
  6. package/dist/dataStoreContext.d.ts.map +1 -1
  7. package/dist/dataStoreContext.js +43 -5
  8. package/dist/dataStoreContext.js.map +1 -1
  9. package/dist/garbageCollection.d.ts +5 -4
  10. package/dist/garbageCollection.d.ts.map +1 -1
  11. package/dist/garbageCollection.js +15 -6
  12. package/dist/garbageCollection.js.map +1 -1
  13. package/dist/garbageCollectionConstants.d.ts +2 -0
  14. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  15. package/dist/garbageCollectionConstants.js +3 -1
  16. package/dist/garbageCollectionConstants.js.map +1 -1
  17. package/dist/opLifecycle/opDecompressor.d.ts +4 -0
  18. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  19. package/dist/opLifecycle/opDecompressor.js +42 -3
  20. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  21. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  22. package/dist/opLifecycle/outbox.js +3 -2
  23. package/dist/opLifecycle/outbox.js.map +1 -1
  24. package/dist/packageVersion.d.ts +1 -1
  25. package/dist/packageVersion.js +1 -1
  26. package/dist/packageVersion.js.map +1 -1
  27. package/dist/summaryFormat.d.ts +19 -0
  28. package/dist/summaryFormat.d.ts.map +1 -1
  29. package/dist/summaryFormat.js.map +1 -1
  30. package/dist/summaryGenerator.d.ts.map +1 -1
  31. package/dist/summaryGenerator.js +1 -1
  32. package/dist/summaryGenerator.js.map +1 -1
  33. package/lib/containerRuntime.d.ts +1 -1
  34. package/lib/containerRuntime.d.ts.map +1 -1
  35. package/lib/containerRuntime.js +58 -34
  36. package/lib/containerRuntime.js.map +1 -1
  37. package/lib/dataStoreContext.d.ts +12 -0
  38. package/lib/dataStoreContext.d.ts.map +1 -1
  39. package/lib/dataStoreContext.js +45 -7
  40. package/lib/dataStoreContext.js.map +1 -1
  41. package/lib/garbageCollection.d.ts +5 -4
  42. package/lib/garbageCollection.d.ts.map +1 -1
  43. package/lib/garbageCollection.js +16 -7
  44. package/lib/garbageCollection.js.map +1 -1
  45. package/lib/garbageCollectionConstants.d.ts +2 -0
  46. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  47. package/lib/garbageCollectionConstants.js +2 -0
  48. package/lib/garbageCollectionConstants.js.map +1 -1
  49. package/lib/opLifecycle/opDecompressor.d.ts +4 -0
  50. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  51. package/lib/opLifecycle/opDecompressor.js +42 -3
  52. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  53. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  54. package/lib/opLifecycle/outbox.js +3 -2
  55. package/lib/opLifecycle/outbox.js.map +1 -1
  56. package/lib/packageVersion.d.ts +1 -1
  57. package/lib/packageVersion.js +1 -1
  58. package/lib/packageVersion.js.map +1 -1
  59. package/lib/summaryFormat.d.ts +19 -0
  60. package/lib/summaryFormat.d.ts.map +1 -1
  61. package/lib/summaryFormat.js.map +1 -1
  62. package/lib/summaryGenerator.d.ts.map +1 -1
  63. package/lib/summaryGenerator.js +1 -1
  64. package/lib/summaryGenerator.js.map +1 -1
  65. package/package.json +19 -53
  66. package/src/containerRuntime.ts +99 -51
  67. package/src/dataStoreContext.ts +64 -5
  68. package/src/garbageCollection.ts +24 -9
  69. package/src/garbageCollectionConstants.ts +3 -0
  70. package/src/opLifecycle/opDecompressor.ts +49 -2
  71. package/src/opLifecycle/outbox.ts +3 -2
  72. package/src/packageVersion.ts +1 -1
  73. package/src/summaryFormat.ts +22 -0
  74. package/src/summaryGenerator.ts +9 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.0.0-internal.3.0.1",
3
+ "version": "2.0.0-internal.3.0.3",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -65,19 +65,19 @@
65
65
  "dependencies": {
66
66
  "@fluidframework/common-definitions": "^0.20.1",
67
67
  "@fluidframework/common-utils": "^1.0.0",
68
- "@fluidframework/container-definitions": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
69
- "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
70
- "@fluidframework/container-utils": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
71
- "@fluidframework/core-interfaces": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
72
- "@fluidframework/datastore": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
73
- "@fluidframework/driver-definitions": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
74
- "@fluidframework/driver-utils": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
75
- "@fluidframework/garbage-collector": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
68
+ "@fluidframework/container-definitions": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
69
+ "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
70
+ "@fluidframework/container-utils": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
71
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
72
+ "@fluidframework/datastore": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
73
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
74
+ "@fluidframework/driver-utils": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
75
+ "@fluidframework/garbage-collector": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
76
76
  "@fluidframework/protocol-base": "^0.1038.2000",
77
77
  "@fluidframework/protocol-definitions": "^1.1.0",
78
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
79
- "@fluidframework/runtime-utils": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
80
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
78
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
79
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
80
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
81
81
  "double-ended-queue": "^2.1.0-0",
82
82
  "events": "^3.1.0",
83
83
  "lz4js": "^0.2.0",
@@ -87,10 +87,10 @@
87
87
  "@fluid-tools/build-cli": "^0.8.0",
88
88
  "@fluidframework/build-common": "^1.1.0",
89
89
  "@fluidframework/build-tools": "^0.8.0",
90
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.2.2.0",
90
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.3.0.0",
91
91
  "@fluidframework/eslint-config-fluid": "^2.0.0",
92
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
93
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.3.0.1 <2.0.0-internal.4.0.0",
92
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
93
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.3.0.3 <2.0.0-internal.4.0.0",
94
94
  "@microsoft/api-extractor": "^7.22.2",
95
95
  "@rushstack/eslint-config": "^2.5.1",
96
96
  "@types/double-ended-queue": "^2.1.0",
@@ -109,43 +109,9 @@
109
109
  "typescript": "~4.5.5"
110
110
  },
111
111
  "typeValidation": {
112
- "version": "2.0.0-internal.3.0.0",
113
- "baselineRange": ">=2.0.0-internal.2.0.0 <2.0.0-internal.3.0.0",
114
- "baselineVersion": "2.0.0-internal.2.2.0",
115
- "broken": {
116
- "RemovedVariableDeclaration_gcBlobPrefix": {
117
- "forwardCompat": false,
118
- "backCompat": false
119
- },
120
- "RemovedVariableDeclaration_gcTombstoneBlobKey": {
121
- "forwardCompat": false,
122
- "backCompat": false
123
- },
124
- "RemovedVariableDeclaration_gcTreeKey": {
125
- "forwardCompat": false,
126
- "backCompat": false
127
- },
128
- "VariableDeclaration_DefaultSummaryConfiguration": {
129
- "backCompat": false
130
- },
131
- "InterfaceDeclaration_ISummaryBaseConfiguration": {
132
- "backCompat": false
133
- },
134
- "TypeAliasDeclaration_ISummaryConfiguration": {
135
- "backCompat": false
136
- },
137
- "InterfaceDeclaration_ISummaryConfigurationDisableHeuristics": {
138
- "backCompat": false
139
- },
140
- "InterfaceDeclaration_ISummaryConfigurationHeuristics": {
141
- "backCompat": false
142
- },
143
- "ClassDeclaration_ContainerRuntime": {
144
- "forwardCompat": false
145
- },
146
- "InterfaceDeclaration_ISummarizerRuntime": {
147
- "backCompat": false
148
- }
149
- }
112
+ "version": "2.0.0-internal.3.0.1",
113
+ "previousVersionStyle": "previousPatch",
114
+ "baselineRange": "2.0.0-internal.3.0.0",
115
+ "broken": {}
150
116
  }
151
117
  }
@@ -94,6 +94,7 @@ import {
94
94
  addSummarizeResultToSummary,
95
95
  addTreeToSummary,
96
96
  createRootSummarizerNodeWithGC,
97
+ IFetchSnapshotResult,
97
98
  IRootSummarizerNodeWithGC,
98
99
  RequestParser,
99
100
  create404Response,
@@ -103,6 +104,7 @@ import {
103
104
  seqFromTree,
104
105
  calculateStats,
105
106
  TelemetryContext,
107
+ ReadAndParseBlob,
106
108
  } from "@fluidframework/runtime-utils";
107
109
  import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
108
110
  import { v4 as uuid } from "uuid";
@@ -1081,7 +1083,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1081
1083
  Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1082
1084
  runtimeOptions.maxBatchSizeInBytes,
1083
1085
  this.mc.logger);
1084
- this.remoteMessageProcessor = new RemoteMessageProcessor(opSplitter, new OpDecompressor());
1086
+ this.remoteMessageProcessor = new RemoteMessageProcessor(
1087
+ opSplitter,
1088
+ new OpDecompressor(this.mc.logger),
1089
+ );
1085
1090
 
1086
1091
  this.handleContext = new ContainerFluidHandleContext("", this);
1087
1092
 
@@ -1800,7 +1805,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1800
1805
  case ContainerMessageType.Rejoin:
1801
1806
  break;
1802
1807
  default:
1803
- assert(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
1808
+ if (runtimeMessage) {
1809
+ const error = DataProcessingError.create(
1810
+ // Former assert 0x3ce
1811
+ "Runtime message of unknown type",
1812
+ "OpProcessing",
1813
+ message,
1814
+ {
1815
+ local,
1816
+ type: message.type,
1817
+ contentType: typeof message.contents,
1818
+ batch: message.metadata?.batch,
1819
+ compression: message.compression,
1820
+ },
1821
+ );
1822
+ this.closeFn(error);
1823
+ throw error;
1824
+ }
1804
1825
  }
1805
1826
 
1806
1827
  // For back-compat, notify only about runtime messages for now.
@@ -2175,12 +2196,21 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2175
2196
  fullGC,
2176
2197
  } = options;
2177
2198
 
2199
+ const telemetryContext = new TelemetryContext();
2200
+ // Add the options that are used to generate this summary to the telemetry context.
2201
+ telemetryContext.setAll("fluid_Summarize", "Options", {
2202
+ fullTree,
2203
+ trackState,
2204
+ runGC,
2205
+ fullGC,
2206
+ runSweep,
2207
+ });
2208
+
2178
2209
  let gcStats: IGCStats | undefined;
2179
2210
  if (runGC) {
2180
- gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
2211
+ gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC }, telemetryContext);
2181
2212
  }
2182
2213
 
2183
- const telemetryContext = new TelemetryContext();
2184
2214
  const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
2185
2215
 
2186
2216
  this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
@@ -2334,8 +2364,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2334
2364
  /** True to generate full GC data */
2335
2365
  fullGC?: boolean;
2336
2366
  },
2367
+ telemetryContext?: ITelemetryContext,
2337
2368
  ): Promise<IGCStats | undefined> {
2338
- return this.garbageCollector.collectGarbage(options);
2369
+ return this.garbageCollector.collectGarbage(options, telemetryContext);
2339
2370
  }
2340
2371
 
2341
2372
  /**
@@ -2835,18 +2866,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2835
2866
  // The call to fetch the snapshot is very expensive and not always needed.
2836
2867
  // It should only be done by the summarizerNode, if required.
2837
2868
  // When fetching from storage we will always get the latest version and do not use the ackHandle.
2838
- const snapshotTreeFetcher = async () => {
2869
+ const fetchLatestSnapshot: () => Promise<IFetchSnapshotResult> = async () => {
2839
2870
  const fetchResult = await this.fetchLatestSnapshotFromStorage(
2840
2871
  summaryLogger,
2841
2872
  {
2842
- eventName: "RefreshLatestSummaryGetSnapshot",
2873
+ eventName: "RefreshLatestSummaryAckFetch",
2843
2874
  ackHandle,
2844
- summaryRefSeq,
2845
- fetchLatest: true,
2875
+ targetSequenceNumber: summaryRefSeq,
2846
2876
  },
2877
+ readAndParseBlob,
2847
2878
  );
2848
2879
 
2849
- const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2850
2880
  /**
2851
2881
  * If the fetched snapshot is older than the one for which the ack was received, close the container.
2852
2882
  * This should never happen because an ack should be sent after the latest summary is updated in the server.
@@ -2857,7 +2887,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2857
2887
  * such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
2858
2888
  * state.
2859
2889
  */
2860
- if (latestSnapshotRefSeq < summaryRefSeq) {
2890
+ if (fetchResult.latestSnapshotRefSeq < summaryRefSeq) {
2861
2891
  const error = DataProcessingError.create(
2862
2892
  "Fetched snapshot is older than the received ack",
2863
2893
  "RefreshLatestSummaryAck",
@@ -2865,44 +2895,36 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2865
2895
  {
2866
2896
  ackHandle,
2867
2897
  summaryRefSeq,
2868
- latestSnapshotRefSeq,
2898
+ latestSnapshotRefSeq: fetchResult.latestSnapshotRefSeq,
2869
2899
  },
2870
2900
  );
2871
2901
  this.closeFn(error);
2872
2902
  throw error;
2873
2903
  }
2874
2904
 
2875
- summaryLogger.sendTelemetryEvent(
2876
- {
2877
- eventName: "LatestSummaryRetrieved",
2878
- ackHandle,
2879
- lastSequenceNumber: latestSnapshotRefSeq,
2880
- targetSequenceNumber: summaryRefSeq,
2881
- });
2882
-
2883
2905
  // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
2884
2906
  // wait for the delta manager to catch up before refreshing the latest Summary.
2885
- await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq,
2886
- summaryLogger);
2907
+ await this.waitForDeltaManagerToCatchup(
2908
+ fetchResult.latestSnapshotRefSeq,
2909
+ summaryLogger,
2910
+ );
2887
2911
 
2888
- return fetchResult.snapshotTree;
2912
+ return {
2913
+ snapshotTree: fetchResult.snapshotTree,
2914
+ snapshotRefSeq: fetchResult.latestSnapshotRefSeq,
2915
+ };
2889
2916
  };
2890
2917
 
2891
2918
  const result = await this.summarizerNode.refreshLatestSummary(
2892
2919
  proposalHandle,
2893
2920
  summaryRefSeq,
2894
- snapshotTreeFetcher,
2921
+ fetchLatestSnapshot,
2895
2922
  readAndParseBlob,
2896
2923
  summaryLogger,
2897
2924
  );
2898
2925
 
2899
2926
  // Notify the garbage collector so it can update its latest summary state.
2900
- await this.garbageCollector.refreshLatestSummary(
2901
- result,
2902
- proposalHandle,
2903
- summaryRefSeq,
2904
- readAndParseBlob,
2905
- );
2927
+ await this.garbageCollector.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
2906
2928
  }
2907
2929
 
2908
2930
  /**
@@ -2914,30 +2936,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2914
2936
  private async refreshLatestSummaryAckFromServer(
2915
2937
  summaryLogger: ITelemetryLogger,
2916
2938
  ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
2917
- const { snapshotTree, versionId } = await this.fetchLatestSnapshotFromStorage(
2918
- summaryLogger,
2919
- {
2920
- eventName: "RefreshLatestSummaryGetSnapshot",
2921
- fetchLatest: true,
2922
- },
2923
- );
2924
-
2925
2939
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
2926
- const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
2927
-
2940
+ const { snapshotTree, versionId, latestSnapshotRefSeq } =
2941
+ await this.fetchLatestSnapshotFromStorage(
2942
+ summaryLogger,
2943
+ {
2944
+ eventName: "RefreshLatestSummaryFromServerFetch",
2945
+ },
2946
+ readAndParseBlob,
2947
+ );
2948
+ const fetchLatestSnapshot: IFetchSnapshotResult = {
2949
+ snapshotTree,
2950
+ snapshotRefSeq: latestSnapshotRefSeq,
2951
+ };
2928
2952
  const result = await this.summarizerNode.refreshLatestSummary(
2929
- undefined,
2953
+ undefined /* proposalHandle */,
2930
2954
  latestSnapshotRefSeq,
2931
- async () => snapshotTree,
2955
+ async () => fetchLatestSnapshot,
2932
2956
  readAndParseBlob,
2933
2957
  summaryLogger,
2934
2958
  );
2935
2959
 
2936
2960
  // Notify the garbage collector so it can update its latest summary state.
2937
2961
  await this.garbageCollector.refreshLatestSummary(
2962
+ undefined /* proposalHandle */,
2938
2963
  result,
2939
- undefined,
2940
- latestSnapshotRefSeq,
2941
2964
  readAndParseBlob,
2942
2965
  )
2943
2966
 
@@ -2947,29 +2970,54 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2947
2970
  private async fetchLatestSnapshotFromStorage(
2948
2971
  logger: ITelemetryLogger,
2949
2972
  event: ITelemetryGenericEvent,
2950
- ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
2973
+ readAndParseBlob: ReadAndParseBlob,
2974
+ ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
2951
2975
  return PerformanceEvent.timedExecAsync(
2952
- logger, event, async (perfEvent: {
2976
+ logger,
2977
+ event,
2978
+ async (perfEvent: {
2953
2979
  end: (arg0: {
2954
2980
  getVersionDuration?: number | undefined;
2955
2981
  getSnapshotDuration?: number | undefined;
2982
+ snapshotRefSeq?: number | undefined;
2983
+ snapshotVersion?: string | undefined;
2956
2984
  }) => void;
2957
2985
  }) => {
2958
- const stats: { getVersionDuration?: number; getSnapshotDuration?: number; } = {};
2986
+ const stats: {
2987
+ getVersionDuration?: number;
2988
+ getSnapshotDuration?: number;
2989
+ snapshotRefSeq?: number;
2990
+ snapshotVersion?: string;
2991
+ } = {};
2959
2992
  const trace = Trace.start();
2960
2993
 
2961
2994
  const versions = await this.storage.getVersions(
2962
- null, 1, "refreshLatestSummaryAckFromServer", FetchSource.noCache);
2963
- assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2995
+ null,
2996
+ 1,
2997
+ "refreshLatestSummaryAckFromServer",
2998
+ FetchSource.noCache,
2999
+ );
3000
+ assert(
3001
+ !!versions && !!versions[0],
3002
+ 0x137 /* "Failed to get version from storage" */,
3003
+ );
2964
3004
  stats.getVersionDuration = trace.trace().duration;
2965
3005
 
2966
3006
  const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
2967
3007
  assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
2968
3008
  stats.getSnapshotDuration = trace.trace().duration;
3009
+ const latestSnapshotRefSeq = await seqFromTree(maybeSnapshot, readAndParseBlob);
3010
+ stats.snapshotRefSeq = latestSnapshotRefSeq;
3011
+ stats.snapshotVersion = versions[0].id;
2969
3012
 
2970
3013
  perfEvent.end(stats);
2971
- return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
2972
- });
3014
+ return {
3015
+ snapshotTree: maybeSnapshot,
3016
+ versionId: versions[0].id,
3017
+ latestSnapshotRefSeq,
3018
+ };
3019
+ },
3020
+ );
2973
3021
  }
2974
3022
 
2975
3023
  public notifyAttaching(snapshot: ISnapshotTreeWithBlobContents) {
@@ -61,9 +61,11 @@ import {
61
61
  import {
62
62
  addBlobToSummary,
63
63
  convertSummaryTreeToITree,
64
+ packagePathToTelemetryProperty,
64
65
  } from "@fluidframework/runtime-utils";
65
66
  import {
66
67
  ChildLogger,
68
+ generateStack,
67
69
  loggerToMonitoringContext,
68
70
  LoggingError,
69
71
  MonitoringContext,
@@ -261,6 +263,13 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
261
263
  private readonly thresholdOpsCounter: ThresholdCounter;
262
264
  private static readonly pendingOpsCountThreshold = 1000;
263
265
 
266
+ /**
267
+ * If the summarizer makes local changes, a telemetry event is logged. This has the potential to be very noisy.
268
+ * So, adding a count of how many telemetry events are logged per data store context. This can be
269
+ * controlled via feature flags.
270
+ */
271
+ private localChangesTelemetryCount: number;
272
+
264
273
  // The used routes of this node as per the last GC run. This is used to update the used routes of the channel
265
274
  // if it realizes after GC is run.
266
275
  private lastUsedRoutes: string[] | undefined;
@@ -318,6 +327,10 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
318
327
  this.throwOnTombstoneUsage =
319
328
  this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
320
329
  this.clientDetails.type !== summarizerClientType;
330
+
331
+ // By default, a data store can log maximum 10 local changes telemetry in summarizer.
332
+ this.localChangesTelemetryCount =
333
+ this.mc.config.getNumber("Fluid.Telemetry.LocalChangesTelemetryCount") ?? 10;
321
334
  }
322
335
 
323
336
  public dispose(): void {
@@ -343,8 +356,15 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
343
356
  this._tombstoned = tombstone;
344
357
  }
345
358
 
346
- private rejectDeferredRealize(reason: string, packageName?: string): never {
347
- throw new LoggingError(reason, { packageName: { value: packageName, tag: TelemetryDataTag.CodeArtifact } });
359
+ private rejectDeferredRealize(
360
+ reason: string,
361
+ failedPkgPath?: string,
362
+ fullPackageName?: readonly string[],
363
+ ): never {
364
+ throw new LoggingError(reason, {
365
+ failedPkgPath: { value: failedPkgPath, tag: TelemetryDataTag.CodeArtifact },
366
+ fullPackageName: packagePathToTelemetryProperty(fullPackageName),
367
+ });
348
368
  }
349
369
 
350
370
  public async realize(): Promise<IFluidDataStoreChannel> {
@@ -358,6 +378,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
358
378
  value: this.id,
359
379
  tag: TelemetryDataTag.CodeArtifact,
360
380
  },
381
+ packageName: packagePathToTelemetryProperty(this.pkg),
361
382
  });
362
383
  this.channelDeferred?.reject(errorWrapped);
363
384
  this.logger.sendErrorEvent({ eventName: "RealizeError" }, errorWrapped);
@@ -377,18 +398,22 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
377
398
  let lastPkg: string | undefined;
378
399
  for (const pkg of packages) {
379
400
  if (!registry) {
380
- this.rejectDeferredRealize("No registry for package", lastPkg);
401
+ this.rejectDeferredRealize("No registry for package", lastPkg, packages);
381
402
  }
382
403
  lastPkg = pkg;
383
404
  entry = await registry.get(pkg);
384
405
  if (!entry) {
385
- this.rejectDeferredRealize("Registry does not contain entry for the package", pkg);
406
+ this.rejectDeferredRealize(
407
+ "Registry does not contain entry for the package",
408
+ pkg,
409
+ packages,
410
+ );
386
411
  }
387
412
  registry = entry.IFluidDataStoreRegistry;
388
413
  }
389
414
  const factory = entry?.IFluidDataStoreFactory;
390
415
  if (factory === undefined) {
391
- this.rejectDeferredRealize("Can't find factory for package", lastPkg);
416
+ this.rejectDeferredRealize("Can't find factory for package", lastPkg, packages);
392
417
  }
393
418
 
394
419
  return { factory, registry };
@@ -627,6 +652,10 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
627
652
  content,
628
653
  type,
629
654
  };
655
+
656
+ // Summarizer clients should not submit messages.
657
+ this.identifyLocalChangeInSummarizer("DataStoreMessageSubmittedInSummarizer", type);
658
+
630
659
  this._containerRuntime.submitDataStoreOp(
631
660
  this.id,
632
661
  fluidDataStoreContent,
@@ -800,6 +829,33 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
800
829
  }
801
830
  }
802
831
 
832
+ /**
833
+ * Summarizer client should not have local changes. These changes can become part of the summary and can break
834
+ * eventual consistency. For example, the next summary (say at ref seq# 100) may contain these changes whereas
835
+ * other clients that are up-to-date till seq# 100 may not have them yet.
836
+ */
837
+ protected identifyLocalChangeInSummarizer(eventName: string, type?: string) {
838
+ if (this.clientDetails.type !== summarizerClientType || this.localChangesTelemetryCount <= 0) {
839
+ return;
840
+ }
841
+
842
+ // Log a telemetry if there are local changes in the summarizer. This will give us data on how often
843
+ // this is happening and which data stores do this. The eventual goal is to disallow local changes
844
+ // in the summarizer and the data will help us plan this.
845
+ this.mc.logger.sendTelemetryEvent({
846
+ eventName,
847
+ type,
848
+ fluidDataStoreId: {
849
+ value: this.id,
850
+ tag: TelemetryDataTag.CodeArtifact,
851
+ },
852
+ packageName: packagePathToTelemetryProperty(this.pkg),
853
+ isSummaryInProgress: this.summarizerNode.isSummaryInProgress?.(),
854
+ stack: generateStack(),
855
+ });
856
+ this.localChangesTelemetryCount--;
857
+ }
858
+
803
859
  public getCreateChildSummarizerNodeFn(id: string, createParam: CreateChildSummarizerNodeParam) {
804
860
  return (
805
861
  summarizeInternal: SummarizeInternalFn,
@@ -922,6 +978,9 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
922
978
  props.makeLocallyVisibleFn,
923
979
  );
924
980
 
981
+ // Summarizer client should not create local data stores.
982
+ this.identifyLocalChangeInSummarizer("DataStoreCreatedInSummarizer");
983
+
925
984
  this.snapshotTree = props.snapshotTree;
926
985
  if (props.isRootDataStore === true) {
927
986
  this.setInMemoryRoot();
@@ -63,7 +63,8 @@ import {
63
63
  runSessionExpiryKey,
64
64
  runSweepKey,
65
65
  stableGCVersion,
66
- trackGCStateKey
66
+ trackGCStateKey,
67
+ gcTombstoneGenerationOptionName
67
68
  } from "./garbageCollectionConstants";
68
69
  import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
69
70
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
@@ -76,6 +77,7 @@ import {
76
77
  dataStoreAttributesBlobName,
77
78
  IGCMetadata,
78
79
  ICreateContainerMetadata,
80
+ GCFeatureMatrix,
79
81
  } from "./summaryFormat";
80
82
 
81
83
  /** The statistics of the system state after a garbage collection run. */
@@ -145,6 +147,7 @@ export interface IGarbageCollector {
145
147
  /** Run garbage collection and update the reference / used state of the system. */
146
148
  collectGarbage(
147
149
  options: { logger?: ITelemetryLogger; runSweep?: boolean; fullGC?: boolean; },
150
+ telemetryContext?: ITelemetryContext,
148
151
  ): Promise<IGCStats | undefined>;
149
152
  /** Summarizes the GC data and returns it as a summary tree. */
150
153
  summarize(
@@ -158,9 +161,8 @@ export interface IGarbageCollector {
158
161
  getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
159
162
  /** Called when the latest summary of the system has been refreshed. */
160
163
  refreshLatestSummary(
161
- result: RefreshSummaryResult,
162
164
  proposalHandle: string | undefined,
163
- summaryRefSeq: number,
165
+ result: RefreshSummaryResult,
164
166
  readAndParseBlob: ReadAndParseBlob,
165
167
  ): Promise<void>;
166
168
  /** Called when a node is updated. Used to detect and log when an inactive node is changed or loaded. */
@@ -418,6 +420,9 @@ export class GarbageCollector implements IGarbageCollector {
418
420
  // This is the version of GC data in the latest summary being tracked.
419
421
  private latestSummaryGCVersion: GCVersion;
420
422
 
423
+ // Feature Support info persisted to this container's summary
424
+ private readonly persistedGcFeatureMatrix: GCFeatureMatrix | undefined;
425
+
421
426
  // Keeps track of the GC state from the last run.
422
427
  private gcDataFromLastRun: IGarbageCollectionData | undefined;
423
428
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
@@ -557,6 +562,7 @@ export class GarbageCollector implements IGarbageCollector {
557
562
  this.sweepTimeoutMs =
558
563
  metadata?.sweepTimeoutMs
559
564
  ?? computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
565
+ this.persistedGcFeatureMatrix = metadata?.gcFeatureMatrix;
560
566
  } else {
561
567
  // Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
562
568
  // scenario but explicitly failing makes it clearer and promotes correct usage.
@@ -581,6 +587,11 @@ export class GarbageCollector implements IGarbageCollector {
581
587
  this.sweepTimeoutMs =
582
588
  testOverrideSweepTimeoutMs
583
589
  ?? computeSweepTimeout(this.sessionExpiryTimeoutMs);
590
+ if (this.gcOptions[gcTombstoneGenerationOptionName] !== undefined) {
591
+ this.persistedGcFeatureMatrix = {
592
+ tombstoneGeneration: this.gcOptions[gcTombstoneGenerationOptionName],
593
+ };
594
+ }
584
595
  }
585
596
 
586
597
  // If session expiry is enabled, we need to close the container when the session expiry timeout expires.
@@ -950,6 +961,7 @@ export class GarbageCollector implements IGarbageCollector {
950
961
  /** True to generate full GC data */
951
962
  fullGC?: boolean;
952
963
  },
964
+ telemetryContext?: ITelemetryContext,
953
965
  ): Promise<IGCStats | undefined> {
954
966
  const fullGC = options.fullGC ?? (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
955
967
  const logger = options.logger
@@ -974,6 +986,9 @@ export class GarbageCollector implements IGarbageCollector {
974
986
  return undefined;
975
987
  }
976
988
 
989
+ // Add the options that are used to run GC to the telemetry context.
990
+ telemetryContext?.setAll("fluid_GC", "Options", { fullGC, runSweep: options.runSweep });
991
+
977
992
  return PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
978
993
  await this.runPreGCSteps();
979
994
 
@@ -1160,6 +1175,7 @@ export class GarbageCollector implements IGarbageCollector {
1160
1175
  * into the metadata blob. If GC is disabled, the gcFeature is 0.
1161
1176
  */
1162
1177
  gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
1178
+ gcFeatureMatrix: this.persistedGcFeatureMatrix,
1163
1179
  sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
1164
1180
  sweepEnabled: this.sweepEnabled,
1165
1181
  sweepTimeoutMs: this.sweepTimeoutMs,
@@ -1179,9 +1195,8 @@ export class GarbageCollector implements IGarbageCollector {
1179
1195
  * is downloaded and should be used to update the state.
1180
1196
  */
1181
1197
  public async refreshLatestSummary(
1182
- result: RefreshSummaryResult,
1183
1198
  proposalHandle: string | undefined,
1184
- summaryRefSeq: number,
1199
+ result: RefreshSummaryResult,
1185
1200
  readAndParseBlob: ReadAndParseBlob,
1186
1201
  ): Promise<void> {
1187
1202
  // If the latest summary was updated and the summary was tracked, this client is the one that generated this
@@ -1208,8 +1223,8 @@ export class GarbageCollector implements IGarbageCollector {
1208
1223
  }
1209
1224
 
1210
1225
  // If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
1211
- const snapshot = result.snapshot;
1212
- const metadataBlobId = snapshot.blobs[metadataBlobName];
1226
+ const snapshotTree = result.snapshotTree;
1227
+ const metadataBlobId = snapshotTree.blobs[metadataBlobName];
1213
1228
  if (metadataBlobId) {
1214
1229
  const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);
1215
1230
  this.latestSummaryGCVersion = getGCVersion(metadata);
@@ -1223,10 +1238,10 @@ export class GarbageCollector implements IGarbageCollector {
1223
1238
  "No reference timestamp when updating GC state from snapshot",
1224
1239
  "refreshLatestSummary",
1225
1240
  undefined,
1226
- { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) },
1241
+ { proposalHandle, summaryRefSeq: result.summaryRefSeq, details: JSON.stringify(this.configs) },
1227
1242
  );
1228
1243
  }
1229
- const gcSnapshotTree = snapshot.trees[gcTreeKey];
1244
+ const gcSnapshotTree = snapshotTree.trees[gcTreeKey];
1230
1245
  // If GC ran in the container that generated this snapshot, it will have a GC tree.
1231
1246
  this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
1232
1247
  let latestGCData: IGarbageCollectionSnapshotData | undefined;
@@ -10,6 +10,9 @@ export const stableGCVersion: GCVersion = 1;
10
10
  /** The current version of garbage collection. */
11
11
  export const currentGCVersion: GCVersion = 2;
12
12
 
13
+ /** This undocumented GC Option (on ContainerRuntime Options) allows an app to disable enforcing GC on old documents by incrementing this value */
14
+ export const gcTombstoneGenerationOptionName = "gcTombstoneGeneration";
15
+
13
16
  // Feature gate key to turn GC on / off.
14
17
  export const runGCKey = "Fluid.GarbageCollection.RunGC";
15
18
  // Feature gate key to turn GC sweep on / off.