@fluidframework/container-runtime 2.0.0-internal.3.0.1 → 2.0.0-internal.3.0.2
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.
- package/dist/containerRuntime.d.ts +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +43 -31
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +12 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +43 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/garbageCollection.d.ts +5 -4
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +15 -6
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +2 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -1
- package/dist/garbageCollectionConstants.js +3 -1
- package/dist/garbageCollectionConstants.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/summaryFormat.d.ts +19 -0
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -1
- package/dist/summaryGenerator.js.map +1 -1
- package/lib/containerRuntime.d.ts +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +43 -31
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +12 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +45 -7
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/garbageCollection.d.ts +5 -4
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +16 -7
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +2 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -1
- package/lib/garbageCollectionConstants.js +2 -0
- package/lib/garbageCollectionConstants.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/summaryFormat.d.ts +19 -0
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -1
- package/lib/summaryGenerator.js.map +1 -1
- package/package.json +19 -53
- package/src/containerRuntime.ts +78 -49
- package/src/dataStoreContext.ts +64 -5
- package/src/garbageCollection.ts +24 -9
- package/src/garbageCollectionConstants.ts +3 -0
- package/src/packageVersion.ts +1 -1
- package/src/summaryFormat.ts +22 -0
- package/src/summaryGenerator.ts +9 -5
package/src/containerRuntime.ts
CHANGED
|
@@ -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";
|
|
@@ -2175,12 +2177,21 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2175
2177
|
fullGC,
|
|
2176
2178
|
} = options;
|
|
2177
2179
|
|
|
2180
|
+
const telemetryContext = new TelemetryContext();
|
|
2181
|
+
// Add the options that are used to generate this summary to the telemetry context.
|
|
2182
|
+
telemetryContext.setAll("fluid_Summarize", "Options", {
|
|
2183
|
+
fullTree,
|
|
2184
|
+
trackState,
|
|
2185
|
+
runGC,
|
|
2186
|
+
fullGC,
|
|
2187
|
+
runSweep,
|
|
2188
|
+
});
|
|
2189
|
+
|
|
2178
2190
|
let gcStats: IGCStats | undefined;
|
|
2179
2191
|
if (runGC) {
|
|
2180
|
-
gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
|
|
2192
|
+
gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC }, telemetryContext);
|
|
2181
2193
|
}
|
|
2182
2194
|
|
|
2183
|
-
const telemetryContext = new TelemetryContext();
|
|
2184
2195
|
const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
|
|
2185
2196
|
|
|
2186
2197
|
this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
|
|
@@ -2334,8 +2345,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2334
2345
|
/** True to generate full GC data */
|
|
2335
2346
|
fullGC?: boolean;
|
|
2336
2347
|
},
|
|
2348
|
+
telemetryContext?: ITelemetryContext,
|
|
2337
2349
|
): Promise<IGCStats | undefined> {
|
|
2338
|
-
return this.garbageCollector.collectGarbage(options);
|
|
2350
|
+
return this.garbageCollector.collectGarbage(options, telemetryContext);
|
|
2339
2351
|
}
|
|
2340
2352
|
|
|
2341
2353
|
/**
|
|
@@ -2835,18 +2847,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2835
2847
|
// The call to fetch the snapshot is very expensive and not always needed.
|
|
2836
2848
|
// It should only be done by the summarizerNode, if required.
|
|
2837
2849
|
// When fetching from storage we will always get the latest version and do not use the ackHandle.
|
|
2838
|
-
const
|
|
2850
|
+
const fetchLatestSnapshot: () => Promise<IFetchSnapshotResult> = async () => {
|
|
2839
2851
|
const fetchResult = await this.fetchLatestSnapshotFromStorage(
|
|
2840
2852
|
summaryLogger,
|
|
2841
2853
|
{
|
|
2842
|
-
eventName: "
|
|
2854
|
+
eventName: "RefreshLatestSummaryAckFetch",
|
|
2843
2855
|
ackHandle,
|
|
2844
|
-
summaryRefSeq,
|
|
2845
|
-
fetchLatest: true,
|
|
2856
|
+
targetSequenceNumber: summaryRefSeq,
|
|
2846
2857
|
},
|
|
2858
|
+
readAndParseBlob,
|
|
2847
2859
|
);
|
|
2848
2860
|
|
|
2849
|
-
const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
|
|
2850
2861
|
/**
|
|
2851
2862
|
* If the fetched snapshot is older than the one for which the ack was received, close the container.
|
|
2852
2863
|
* This should never happen because an ack should be sent after the latest summary is updated in the server.
|
|
@@ -2857,7 +2868,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2857
2868
|
* such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
|
|
2858
2869
|
* state.
|
|
2859
2870
|
*/
|
|
2860
|
-
if (latestSnapshotRefSeq < summaryRefSeq) {
|
|
2871
|
+
if (fetchResult.latestSnapshotRefSeq < summaryRefSeq) {
|
|
2861
2872
|
const error = DataProcessingError.create(
|
|
2862
2873
|
"Fetched snapshot is older than the received ack",
|
|
2863
2874
|
"RefreshLatestSummaryAck",
|
|
@@ -2865,44 +2876,36 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2865
2876
|
{
|
|
2866
2877
|
ackHandle,
|
|
2867
2878
|
summaryRefSeq,
|
|
2868
|
-
latestSnapshotRefSeq,
|
|
2879
|
+
latestSnapshotRefSeq: fetchResult.latestSnapshotRefSeq,
|
|
2869
2880
|
},
|
|
2870
2881
|
);
|
|
2871
2882
|
this.closeFn(error);
|
|
2872
2883
|
throw error;
|
|
2873
2884
|
}
|
|
2874
2885
|
|
|
2875
|
-
summaryLogger.sendTelemetryEvent(
|
|
2876
|
-
{
|
|
2877
|
-
eventName: "LatestSummaryRetrieved",
|
|
2878
|
-
ackHandle,
|
|
2879
|
-
lastSequenceNumber: latestSnapshotRefSeq,
|
|
2880
|
-
targetSequenceNumber: summaryRefSeq,
|
|
2881
|
-
});
|
|
2882
|
-
|
|
2883
2886
|
// In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
|
|
2884
2887
|
// wait for the delta manager to catch up before refreshing the latest Summary.
|
|
2885
|
-
await this.waitForDeltaManagerToCatchup(
|
|
2886
|
-
|
|
2888
|
+
await this.waitForDeltaManagerToCatchup(
|
|
2889
|
+
fetchResult.latestSnapshotRefSeq,
|
|
2890
|
+
summaryLogger,
|
|
2891
|
+
);
|
|
2887
2892
|
|
|
2888
|
-
return
|
|
2893
|
+
return {
|
|
2894
|
+
snapshotTree: fetchResult.snapshotTree,
|
|
2895
|
+
snapshotRefSeq: fetchResult.latestSnapshotRefSeq,
|
|
2896
|
+
};
|
|
2889
2897
|
};
|
|
2890
2898
|
|
|
2891
2899
|
const result = await this.summarizerNode.refreshLatestSummary(
|
|
2892
2900
|
proposalHandle,
|
|
2893
2901
|
summaryRefSeq,
|
|
2894
|
-
|
|
2902
|
+
fetchLatestSnapshot,
|
|
2895
2903
|
readAndParseBlob,
|
|
2896
2904
|
summaryLogger,
|
|
2897
2905
|
);
|
|
2898
2906
|
|
|
2899
2907
|
// 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
|
-
);
|
|
2908
|
+
await this.garbageCollector.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
|
|
2906
2909
|
}
|
|
2907
2910
|
|
|
2908
2911
|
/**
|
|
@@ -2914,30 +2917,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2914
2917
|
private async refreshLatestSummaryAckFromServer(
|
|
2915
2918
|
summaryLogger: ITelemetryLogger,
|
|
2916
2919
|
): 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
2920
|
const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2921
|
+
const { snapshotTree, versionId, latestSnapshotRefSeq } =
|
|
2922
|
+
await this.fetchLatestSnapshotFromStorage(
|
|
2923
|
+
summaryLogger,
|
|
2924
|
+
{
|
|
2925
|
+
eventName: "RefreshLatestSummaryFromServerFetch",
|
|
2926
|
+
},
|
|
2927
|
+
readAndParseBlob,
|
|
2928
|
+
);
|
|
2929
|
+
const fetchLatestSnapshot: IFetchSnapshotResult = {
|
|
2930
|
+
snapshotTree,
|
|
2931
|
+
snapshotRefSeq: latestSnapshotRefSeq,
|
|
2932
|
+
};
|
|
2928
2933
|
const result = await this.summarizerNode.refreshLatestSummary(
|
|
2929
|
-
undefined
|
|
2934
|
+
undefined /* proposalHandle */,
|
|
2930
2935
|
latestSnapshotRefSeq,
|
|
2931
|
-
async () =>
|
|
2936
|
+
async () => fetchLatestSnapshot,
|
|
2932
2937
|
readAndParseBlob,
|
|
2933
2938
|
summaryLogger,
|
|
2934
2939
|
);
|
|
2935
2940
|
|
|
2936
2941
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2937
2942
|
await this.garbageCollector.refreshLatestSummary(
|
|
2943
|
+
undefined /* proposalHandle */,
|
|
2938
2944
|
result,
|
|
2939
|
-
undefined,
|
|
2940
|
-
latestSnapshotRefSeq,
|
|
2941
2945
|
readAndParseBlob,
|
|
2942
2946
|
)
|
|
2943
2947
|
|
|
@@ -2947,29 +2951,54 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2947
2951
|
private async fetchLatestSnapshotFromStorage(
|
|
2948
2952
|
logger: ITelemetryLogger,
|
|
2949
2953
|
event: ITelemetryGenericEvent,
|
|
2950
|
-
|
|
2954
|
+
readAndParseBlob: ReadAndParseBlob,
|
|
2955
|
+
): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
|
|
2951
2956
|
return PerformanceEvent.timedExecAsync(
|
|
2952
|
-
logger,
|
|
2957
|
+
logger,
|
|
2958
|
+
event,
|
|
2959
|
+
async (perfEvent: {
|
|
2953
2960
|
end: (arg0: {
|
|
2954
2961
|
getVersionDuration?: number | undefined;
|
|
2955
2962
|
getSnapshotDuration?: number | undefined;
|
|
2963
|
+
snapshotRefSeq?: number | undefined;
|
|
2964
|
+
snapshotVersion?: string | undefined;
|
|
2956
2965
|
}) => void;
|
|
2957
2966
|
}) => {
|
|
2958
|
-
const stats: {
|
|
2967
|
+
const stats: {
|
|
2968
|
+
getVersionDuration?: number;
|
|
2969
|
+
getSnapshotDuration?: number;
|
|
2970
|
+
snapshotRefSeq?: number;
|
|
2971
|
+
snapshotVersion?: string;
|
|
2972
|
+
} = {};
|
|
2959
2973
|
const trace = Trace.start();
|
|
2960
2974
|
|
|
2961
2975
|
const versions = await this.storage.getVersions(
|
|
2962
|
-
null,
|
|
2963
|
-
|
|
2976
|
+
null,
|
|
2977
|
+
1,
|
|
2978
|
+
"refreshLatestSummaryAckFromServer",
|
|
2979
|
+
FetchSource.noCache,
|
|
2980
|
+
);
|
|
2981
|
+
assert(
|
|
2982
|
+
!!versions && !!versions[0],
|
|
2983
|
+
0x137 /* "Failed to get version from storage" */,
|
|
2984
|
+
);
|
|
2964
2985
|
stats.getVersionDuration = trace.trace().duration;
|
|
2965
2986
|
|
|
2966
2987
|
const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
|
|
2967
2988
|
assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
|
|
2968
2989
|
stats.getSnapshotDuration = trace.trace().duration;
|
|
2990
|
+
const latestSnapshotRefSeq = await seqFromTree(maybeSnapshot, readAndParseBlob);
|
|
2991
|
+
stats.snapshotRefSeq = latestSnapshotRefSeq;
|
|
2992
|
+
stats.snapshotVersion = versions[0].id;
|
|
2969
2993
|
|
|
2970
2994
|
perfEvent.end(stats);
|
|
2971
|
-
return {
|
|
2972
|
-
|
|
2995
|
+
return {
|
|
2996
|
+
snapshotTree: maybeSnapshot,
|
|
2997
|
+
versionId: versions[0].id,
|
|
2998
|
+
latestSnapshotRefSeq,
|
|
2999
|
+
};
|
|
3000
|
+
},
|
|
3001
|
+
);
|
|
2973
3002
|
}
|
|
2974
3003
|
|
|
2975
3004
|
public notifyAttaching(snapshot: ISnapshotTreeWithBlobContents) {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -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(
|
|
347
|
-
|
|
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(
|
|
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();
|
package/src/garbageCollection.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
1212
|
-
const metadataBlobId =
|
|
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 =
|
|
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.
|
package/src/packageVersion.ts
CHANGED
package/src/summaryFormat.ts
CHANGED
|
@@ -89,15 +89,37 @@ export interface ICreateContainerMetadata {
|
|
|
89
89
|
createContainerTimestamp?: number;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** @see IGCMetadata.gcFeatureMatrix */
|
|
93
|
+
export interface GCFeatureMatrix {
|
|
94
|
+
/**
|
|
95
|
+
* The Tombstone Generation value in effect when this file was created.
|
|
96
|
+
* Gives a way for an app to disqualify old files from GC Tombstone enforcement
|
|
97
|
+
* Provided via Container Runtime Options
|
|
98
|
+
*/
|
|
99
|
+
tombstoneGeneration?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
export type GCVersion = number;
|
|
93
103
|
export interface IGCMetadata {
|
|
94
104
|
/**
|
|
95
105
|
* The version of the GC code that was run to generate the GC data that is written in the summary.
|
|
106
|
+
* If the persisted value doesn't match the current value in the code, saved GC data will be discarded and regenerated from scratch.
|
|
96
107
|
* Also, used to determine whether GC is enabled for this container or not:
|
|
97
108
|
* - A value of 0 or undefined means GC is disabled.
|
|
98
109
|
* - A value greater than 0 means GC is enabled.
|
|
99
110
|
*/
|
|
100
111
|
readonly gcFeature?: GCVersion;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Numerical indication of feature support as of file creation time, used to determine feature availability over time.
|
|
115
|
+
* This info may come from multiple sources (FF code, config service, app via Container Runtime Options),
|
|
116
|
+
* and pertains to aspects of the document that may be fixed for its lifetime.
|
|
117
|
+
*
|
|
118
|
+
* For each dimension, if the persisted value is less than the currently provided value,
|
|
119
|
+
* then this file does not support the corresponding feature as currently implemented.
|
|
120
|
+
*/
|
|
121
|
+
readonly gcFeatureMatrix?: GCFeatureMatrix;
|
|
122
|
+
|
|
101
123
|
/**
|
|
102
124
|
* Tells whether the GC sweep phase is enabled for this container.
|
|
103
125
|
* - True means sweep phase is enabled.
|
package/src/summaryGenerator.ts
CHANGED
|
@@ -205,11 +205,15 @@ export class SummaryGenerator {
|
|
|
205
205
|
timeSinceLastSummary,
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
-
const summarizeEvent = PerformanceEvent.start(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
const summarizeEvent = PerformanceEvent.start(
|
|
209
|
+
logger,
|
|
210
|
+
{
|
|
211
|
+
eventName: "Summarize",
|
|
212
|
+
refreshLatestAck,
|
|
213
|
+
...summarizeTelemetryProps,
|
|
214
|
+
},
|
|
215
|
+
{ start: true, end: true, cancel: "generic" },
|
|
216
|
+
);
|
|
213
217
|
|
|
214
218
|
// Helper functions to report failures and return.
|
|
215
219
|
const getFailMessage =
|