@fluidframework/container-runtime 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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.
- package/CHANGELOG.md +4 -0
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +3 -2
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +1 -0
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +20 -11
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +1 -2
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +5 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -6
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +56 -70
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +227 -408
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +8 -10
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +2 -0
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts +11 -11
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +18 -22
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +6 -2
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +16 -6
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +91 -0
- package/dist/gc/gcTelemetry.d.ts.map +1 -0
- package/dist/gc/gcTelemetry.js +282 -0
- package/dist/gc/gcTelemetry.js.map +1 -0
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +5 -6
- package/dist/gc/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +1 -1
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.js +1 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +25 -22
- package/dist/opLifecycle/remoteMessageProcessor.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/pendingStateManager.d.ts +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.js +15 -4
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js +14 -17
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/summarizer.d.ts +2 -0
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +9 -4
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerHeuristics.d.ts +8 -9
- package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summary/summarizerHeuristics.js +15 -16
- package/dist/summary/summarizerHeuristics.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +2 -0
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js +4 -3
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +3 -2
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +1 -0
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +20 -11
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +5 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -6
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +56 -70
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +231 -412
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +8 -10
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +2 -0
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts +11 -11
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +16 -20
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +6 -2
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +16 -6
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +91 -0
- package/lib/gc/gcTelemetry.d.ts.map +1 -0
- package/lib/gc/gcTelemetry.js +277 -0
- package/lib/gc/gcTelemetry.js.map +1 -0
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +1 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.js +1 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +25 -22
- package/lib/opLifecycle/remoteMessageProcessor.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/pendingStateManager.d.ts +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.js +15 -4
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js +14 -17
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/summarizer.d.ts +2 -0
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +9 -4
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerHeuristics.d.ts +8 -9
- package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summary/summarizerHeuristics.js +15 -16
- package/lib/summary/summarizerHeuristics.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +2 -0
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js +4 -3
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/package.json +15 -16
- package/src/blobManager.ts +3 -2
- package/src/connectionTelemetry.ts +1 -0
- package/src/containerRuntime.ts +22 -15
- package/src/dataStoreContext.ts +1 -2
- package/src/dataStores.ts +4 -7
- package/src/gc/garbageCollection.ts +316 -561
- package/src/gc/gcConfigs.ts +12 -11
- package/src/gc/gcDefinitions.ts +2 -0
- package/src/gc/gcHelpers.ts +21 -40
- package/src/gc/gcSummaryStateTracker.ts +19 -7
- package/src/gc/gcTelemetry.ts +408 -0
- package/src/gc/index.ts +2 -6
- package/src/opLifecycle/README.md +13 -0
- package/src/opLifecycle/opGroupingManager.ts +1 -1
- package/src/opLifecycle/outbox.ts +2 -2
- package/src/opLifecycle/remoteMessageProcessor.ts +37 -28
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +1 -4
- package/src/scheduleManager.ts +19 -7
- package/src/summary/orderedClientElection.ts +14 -17
- package/src/summary/summarizer.ts +17 -5
- package/src/summary/summarizerHeuristics.ts +15 -16
- package/src/summary/summarizerTypes.ts +2 -0
- package/src/summary/summaryGenerator.ts +5 -4
- package/dist/gc/gcSweepReadyUsageDetection.d.ts +0 -53
- package/dist/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
- package/dist/gc/gcSweepReadyUsageDetection.js +0 -130
- package/dist/gc/gcSweepReadyUsageDetection.js.map +0 -1
- package/lib/gc/gcSweepReadyUsageDetection.d.ts +0 -53
- package/lib/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
- package/lib/gc/gcSweepReadyUsageDetection.js +0 -125
- package/lib/gc/gcSweepReadyUsageDetection.js.map +0 -1
- package/src/gc/gcSweepReadyUsageDetection.ts +0 -145
package/src/gc/gcConfigs.ts
CHANGED
|
@@ -106,21 +106,26 @@ export function generateGCConfigs(
|
|
|
106
106
|
createParams.gcOptions[gcSweepGenerationOptionName] /* currentGeneration */,
|
|
107
107
|
);
|
|
108
108
|
|
|
109
|
+
// If version upgrade is not enabled, fall back to the stable GC version.
|
|
110
|
+
const gcVersionInEffect =
|
|
111
|
+
mc.config.getBoolean(gcVersionUpgradeToV3Key) === true ? currentGCVersion : stableGCVersion;
|
|
112
|
+
|
|
113
|
+
// The GC version is up-to-date if the GC version in effect is at least equal to the GC version in base snapshot.
|
|
114
|
+
// If it is not up-to-date, there is a newer version of GC out there which is more reliable than this. So, GC
|
|
115
|
+
// should not run as it may produce incorrect / unreliable state.
|
|
116
|
+
const isGCVersionUpToDate =
|
|
117
|
+
gcVersionInBaseSnapshot === undefined || gcVersionInEffect >= gcVersionInBaseSnapshot;
|
|
118
|
+
|
|
109
119
|
/**
|
|
110
120
|
* Whether GC should run or not. The following conditions have to be met to run sweep:
|
|
111
|
-
*
|
|
112
121
|
* 1. GC should be enabled for this container.
|
|
113
|
-
*
|
|
114
122
|
* 2. GC should not be disabled via disableGC GC option.
|
|
115
|
-
*
|
|
123
|
+
* 3. The current GC version should be greater of equal to the GC version in the base snapshot.
|
|
116
124
|
* These conditions can be overridden via runGCKey feature flag.
|
|
117
125
|
*/
|
|
118
126
|
const shouldRunGC =
|
|
119
127
|
mc.config.getBoolean(runGCKey) ??
|
|
120
|
-
|
|
121
|
-
(gcEnabled &&
|
|
122
|
-
// GC must not be disabled via GC options.
|
|
123
|
-
!createParams.gcOptions.disableGC);
|
|
128
|
+
(gcEnabled && !createParams.gcOptions.disableGC && isGCVersionUpToDate);
|
|
124
129
|
|
|
125
130
|
/**
|
|
126
131
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
@@ -156,10 +161,6 @@ export function generateGCConfigs(
|
|
|
156
161
|
const tombstoneMode = !shouldRunSweep && mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
157
162
|
const runFullGC = createParams.gcOptions.runFullGC;
|
|
158
163
|
|
|
159
|
-
// If version upgrade is not enabled, fall back to the stable GC version.
|
|
160
|
-
const gcVersionInEffect =
|
|
161
|
-
mc.config.getBoolean(gcVersionUpgradeToV3Key) === true ? currentGCVersion : stableGCVersion;
|
|
162
|
-
|
|
163
164
|
return {
|
|
164
165
|
gcEnabled,
|
|
165
166
|
sweepEnabled,
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -204,6 +204,8 @@ export interface IGarbageCollector {
|
|
|
204
204
|
readonly shouldRunGC: boolean;
|
|
205
205
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
206
206
|
readonly summaryStateNeedsReset: boolean;
|
|
207
|
+
/** The count of data stores whose GC state updated since the last summary. */
|
|
208
|
+
readonly updatedDSCountSinceLastSummary: number;
|
|
207
209
|
/** Initialize the state from the base snapshot after its creation. */
|
|
208
210
|
initializeBaseState(): Promise<void>;
|
|
209
211
|
/** Run garbage collection and update the reference / used state of the system. */
|
package/src/gc/gcHelpers.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryGenericEvent } from "@fluidframework/common-definitions";
|
|
7
6
|
import { assert } from "@fluidframework/common-utils";
|
|
8
7
|
import { ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
9
8
|
import {
|
|
@@ -13,17 +12,8 @@ import {
|
|
|
13
12
|
IGarbageCollectionData,
|
|
14
13
|
IGarbageCollectionDetailsBase,
|
|
15
14
|
} from "@fluidframework/runtime-definitions";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
disableTombstoneKey,
|
|
20
|
-
GCFeatureMatrix,
|
|
21
|
-
GCVersion,
|
|
22
|
-
IGCMetadata,
|
|
23
|
-
runSweepKey,
|
|
24
|
-
throwOnTombstoneLoadKey,
|
|
25
|
-
throwOnTombstoneUsageKey,
|
|
26
|
-
} from "./gcDefinitions";
|
|
15
|
+
import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
16
|
+
import { GCFeatureMatrix, GCVersion, IGCMetadata } from "./gcDefinitions";
|
|
27
17
|
import {
|
|
28
18
|
IGarbageCollectionNodeData,
|
|
29
19
|
IGarbageCollectionSnapshotData,
|
|
@@ -38,32 +28,6 @@ export function getGCVersion(metadata?: IGCMetadata): GCVersion {
|
|
|
38
28
|
return metadata.gcFeature ?? 0;
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
/**
|
|
42
|
-
* Consolidates info / logic for logging when we encounter unexpected usage of GC'd objects. For example, when a
|
|
43
|
-
* tombstoned or deleted object is loaded.
|
|
44
|
-
*/
|
|
45
|
-
export function sendGCUnexpectedUsageEvent(
|
|
46
|
-
mc: MonitoringContext,
|
|
47
|
-
event: ITelemetryGenericEvent & {
|
|
48
|
-
category: "error" | "generic";
|
|
49
|
-
gcTombstoneEnforcementAllowed: boolean | undefined;
|
|
50
|
-
},
|
|
51
|
-
packagePath: readonly string[] | undefined,
|
|
52
|
-
error?: unknown,
|
|
53
|
-
) {
|
|
54
|
-
event.pkg = packagePathToTelemetryProperty(packagePath);
|
|
55
|
-
event.tombstoneFlags = JSON.stringify({
|
|
56
|
-
DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
|
|
57
|
-
ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
58
|
-
ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadKey),
|
|
59
|
-
});
|
|
60
|
-
event.sweepFlags = JSON.stringify({
|
|
61
|
-
EnableSweepFlag: mc.config.getBoolean(runSweepKey),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
mc.logger.sendTelemetryEvent(event, error);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
31
|
/**
|
|
68
32
|
* Indicates whether Tombstone Enforcement is allowed for this document based on the current/persisted
|
|
69
33
|
* TombstoneGeneration values
|
|
@@ -188,12 +152,19 @@ export function concatGarbageCollectionStates(
|
|
|
188
152
|
/**
|
|
189
153
|
* Helper function that clones the GC data.
|
|
190
154
|
* @param gcData - The GC data to clone.
|
|
155
|
+
* @param filter - Optional function to filter out node ids not to be included in the cloned GC data. Returns
|
|
156
|
+
* true to filter out nodes.
|
|
191
157
|
* @returns a clone of the given GC data.
|
|
192
158
|
*/
|
|
193
|
-
export function cloneGCData(
|
|
159
|
+
export function cloneGCData(
|
|
160
|
+
gcData: IGarbageCollectionData,
|
|
161
|
+
filter?: (id: string) => boolean,
|
|
162
|
+
): IGarbageCollectionData {
|
|
194
163
|
const clonedGCNodes: { [id: string]: string[] } = {};
|
|
195
164
|
for (const [id, outboundRoutes] of Object.entries(gcData.gcNodes)) {
|
|
196
|
-
|
|
165
|
+
if (filter?.(id) !== true) {
|
|
166
|
+
clonedGCNodes[id] = Array.from(outboundRoutes);
|
|
167
|
+
}
|
|
197
168
|
}
|
|
198
169
|
return {
|
|
199
170
|
gcNodes: clonedGCNodes,
|
|
@@ -333,3 +304,13 @@ export function unpackChildNodesGCDetails(gcDetails: IGarbageCollectionDetailsBa
|
|
|
333
304
|
export function trimLeadingAndTrailingSlashes(str: string) {
|
|
334
305
|
return str.replace(/^\/+|\/+$/g, "");
|
|
335
306
|
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Tags the passed value as a CodeArtifact and returns the tagged value.
|
|
310
|
+
*/
|
|
311
|
+
export function tagAsCodeArtifact(value: string) {
|
|
312
|
+
return {
|
|
313
|
+
value,
|
|
314
|
+
tag: TelemetryDataTag.CodeArtifact,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "@fluidframework/runtime-definitions";
|
|
15
15
|
import { mergeStats, ReadAndParseBlob, SummaryTreeBuilder } from "@fluidframework/runtime-utils";
|
|
16
16
|
import { IContainerRuntimeMetadata, metadataBlobName, RefreshSummaryResult } from "../summary";
|
|
17
|
-
import { GCVersion } from "./gcDefinitions";
|
|
17
|
+
import { GCVersion, IGCStats } from "./gcDefinitions";
|
|
18
18
|
import { getGCDataFromSnapshot, generateSortedGCState, getGCVersion } from "./gcHelpers";
|
|
19
19
|
import { IGarbageCollectionSnapshotData, IGarbageCollectionState } from "./gcSummaryDefinitions";
|
|
20
20
|
import { IGarbageCollectorConfigs } from ".";
|
|
@@ -37,8 +37,6 @@ export interface IGCSummaryTrackingData {
|
|
|
37
37
|
* On summarize, it decides whether to write new state or re-use previous summary's state.
|
|
38
38
|
*/
|
|
39
39
|
export class GCSummaryStateTracker {
|
|
40
|
-
// The current version of GC running.
|
|
41
|
-
public readonly currentGCVersion: GCVersion = this.configs.gcVersionInEffect;
|
|
42
40
|
// This is the version of GC data in the latest summary being tracked.
|
|
43
41
|
private latestSummaryGCVersion: GCVersion;
|
|
44
42
|
|
|
@@ -50,6 +48,10 @@ export class GCSummaryStateTracker {
|
|
|
50
48
|
// Tracks whether there was GC was run in latest summary being tracked.
|
|
51
49
|
private wasGCRunInLatestSummary: boolean;
|
|
52
50
|
|
|
51
|
+
// Tracks the count of data stores whose state updated since the last summary, i.e., they went from referenced
|
|
52
|
+
// to unreferenced or vice-versa.
|
|
53
|
+
public updatedDSCountSinceLastSummary: number = 0;
|
|
54
|
+
|
|
53
55
|
constructor(
|
|
54
56
|
// Tells whether GC should run or not.
|
|
55
57
|
private readonly configs: Pick<
|
|
@@ -62,7 +64,8 @@ export class GCSummaryStateTracker {
|
|
|
62
64
|
this.wasGCRunInLatestSummary = wasGCRunInBaseSnapshot;
|
|
63
65
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
64
66
|
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
65
|
-
this.latestSummaryGCVersion =
|
|
67
|
+
this.latestSummaryGCVersion =
|
|
68
|
+
this.configs.gcVersionInBaseSnapshot ?? this.configs.gcVersionInEffect;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
/**
|
|
@@ -101,7 +104,8 @@ export class GCSummaryStateTracker {
|
|
|
101
104
|
public get doesSummaryStateNeedReset(): boolean {
|
|
102
105
|
return (
|
|
103
106
|
this.doesGCStateNeedReset ||
|
|
104
|
-
(this.configs.shouldRunGC &&
|
|
107
|
+
(this.configs.shouldRunGC &&
|
|
108
|
+
this.latestSummaryGCVersion !== this.configs.gcVersionInEffect)
|
|
105
109
|
);
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -283,9 +287,10 @@ export class GCSummaryStateTracker {
|
|
|
283
287
|
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
284
288
|
// Update latest state from pending.
|
|
285
289
|
if (result.wasSummaryTracked) {
|
|
286
|
-
this.latestSummaryGCVersion = this.
|
|
290
|
+
this.latestSummaryGCVersion = this.configs.gcVersionInEffect;
|
|
287
291
|
this.latestSummaryData = this.pendingSummaryData;
|
|
288
292
|
this.pendingSummaryData = undefined;
|
|
293
|
+
this.updatedDSCountSinceLastSummary = 0;
|
|
289
294
|
return undefined;
|
|
290
295
|
}
|
|
291
296
|
|
|
@@ -311,7 +316,7 @@ export class GCSummaryStateTracker {
|
|
|
311
316
|
// in the snapshot cannot be interpreted correctly. Set everything to undefined except for deletedNodes
|
|
312
317
|
// because irrespective of GC versions, these nodes have been deleted and cannot be brought back. The
|
|
313
318
|
// deletedNodes info is needed to identify when these nodes are used.
|
|
314
|
-
if (getGCVersion(metadata) !== this.
|
|
319
|
+
if (getGCVersion(metadata) !== this.configs.gcVersionInEffect) {
|
|
315
320
|
snapshotData = {
|
|
316
321
|
gcState: undefined,
|
|
317
322
|
tombstones: undefined,
|
|
@@ -326,4 +331,11 @@ export class GCSummaryStateTracker {
|
|
|
326
331
|
};
|
|
327
332
|
return snapshotData;
|
|
328
333
|
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Called to update the state from a GC run's stats. Used to update the count of data stores whose state updated.
|
|
337
|
+
*/
|
|
338
|
+
public updateStateFromGCRunStats(stats: IGCStats) {
|
|
339
|
+
this.updatedDSCountSinceLastSummary += stats.updatedDataStoreCount;
|
|
340
|
+
}
|
|
329
341
|
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
+
import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
|
|
8
|
+
import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
|
|
9
|
+
import { generateStack, MonitoringContext } from "@fluidframework/telemetry-utils";
|
|
10
|
+
import { ICreateContainerMetadata } from "../summary";
|
|
11
|
+
import {
|
|
12
|
+
disableSweepLogKey,
|
|
13
|
+
GCNodeType,
|
|
14
|
+
UnreferencedState,
|
|
15
|
+
IGarbageCollectorConfigs,
|
|
16
|
+
disableTombstoneKey,
|
|
17
|
+
throwOnTombstoneUsageKey,
|
|
18
|
+
throwOnTombstoneLoadKey,
|
|
19
|
+
runSweepKey,
|
|
20
|
+
} from "./gcDefinitions";
|
|
21
|
+
import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
22
|
+
import { tagAsCodeArtifact } from "./gcHelpers";
|
|
23
|
+
|
|
24
|
+
type NodeUsageType = "Changed" | "Loaded" | "Revived";
|
|
25
|
+
|
|
26
|
+
/** Properties that are common to IUnreferencedEventProps and INodeUsageProps */
|
|
27
|
+
interface ICommonProps {
|
|
28
|
+
usageType: NodeUsageType;
|
|
29
|
+
completedGCRuns: number;
|
|
30
|
+
isTombstoned: boolean;
|
|
31
|
+
lastSummaryTime?: number;
|
|
32
|
+
viaHandle?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The event that is logged when unreferenced node is used after a certain time. */
|
|
36
|
+
interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps {
|
|
37
|
+
state: UnreferencedState;
|
|
38
|
+
id: {
|
|
39
|
+
value: string;
|
|
40
|
+
tag: string;
|
|
41
|
+
};
|
|
42
|
+
type: GCNodeType;
|
|
43
|
+
unrefTime: number;
|
|
44
|
+
age: number;
|
|
45
|
+
timeout?: number;
|
|
46
|
+
fromId?: {
|
|
47
|
+
value: string;
|
|
48
|
+
tag: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Properties passed to nodeUsed function when a node is used. */
|
|
53
|
+
interface INodeUsageProps extends ICommonProps {
|
|
54
|
+
id: string;
|
|
55
|
+
currentReferenceTimestampMs: number | undefined;
|
|
56
|
+
packagePath: readonly string[] | undefined;
|
|
57
|
+
fromId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Encapsulates the logic that tracks the various telemetry logged by the Garbage Collector. There are 4 types of
|
|
62
|
+
* telemetry logged:
|
|
63
|
+
* 1. inactiveObject telemetry - When an inactive node is used - A node that has been unreferenced for inactiveTimeoutMs.
|
|
64
|
+
* 2. sweepReadyObject telemetry - When a sweep ready node is used - A node that has been unreferenced for sweepTimeoutMs.
|
|
65
|
+
* 3. Tombstone telemetry - When a tombstoned node is used - A node that that has been marked as tombstone.
|
|
66
|
+
* 4. Sweep / deleted telemetry - When a node is detected as sweep ready in the sweep phase.
|
|
67
|
+
* 5. Unknown outbound reference telemetry - When a node is referenced but GC is not explicitly notified of it.
|
|
68
|
+
*/
|
|
69
|
+
export class GCTelemetryTracker {
|
|
70
|
+
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
71
|
+
// per event per node.
|
|
72
|
+
private readonly loggedUnreferencedEvents: Set<string> = new Set();
|
|
73
|
+
// Queue for unreferenced events that should be logged the next time GC runs.
|
|
74
|
+
private pendingEventsQueue: IUnreferencedEventProps[] = [];
|
|
75
|
+
|
|
76
|
+
constructor(
|
|
77
|
+
private readonly mc: MonitoringContext,
|
|
78
|
+
private readonly configs: Pick<
|
|
79
|
+
IGarbageCollectorConfigs,
|
|
80
|
+
"inactiveTimeoutMs" | "sweepTimeoutMs"
|
|
81
|
+
>,
|
|
82
|
+
private readonly isSummarizerClient: boolean,
|
|
83
|
+
private readonly gcTombstoneEnforcementAllowed: boolean,
|
|
84
|
+
private readonly createContainerMetadata: ICreateContainerMetadata,
|
|
85
|
+
private readonly getNodeType: (nodeId: string) => GCNodeType,
|
|
86
|
+
private readonly getNodeStateTracker: (
|
|
87
|
+
nodeId: string,
|
|
88
|
+
) => UnreferencedStateTracker | undefined,
|
|
89
|
+
private readonly getNodePackagePath: (
|
|
90
|
+
nodePath: string,
|
|
91
|
+
) => Promise<readonly string[] | undefined>,
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns whether an event should be logged for a node that isn't active anymore. Some scenarios where we won't log:
|
|
96
|
+
* 1. When a DDS is changed or loaded. The corresponding data store's event will be logged instead.
|
|
97
|
+
* 2. An event is logged only once per container instance per event per node.
|
|
98
|
+
*/
|
|
99
|
+
private shouldLogNonActiveEvent(
|
|
100
|
+
nodeId: string,
|
|
101
|
+
nodeType: GCNodeType,
|
|
102
|
+
usageType: NodeUsageType,
|
|
103
|
+
nodeStateTracker: UnreferencedStateTracker,
|
|
104
|
+
uniqueEventId: string,
|
|
105
|
+
) {
|
|
106
|
+
if (nodeStateTracker.state === UnreferencedState.Active) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// For sub data store (DDS) nodes, if they are changed or loaded, its data store will also be changed or loaded,
|
|
111
|
+
// so skip logging to make the telemetry less noisy.
|
|
112
|
+
if (nodeType === GCNodeType.SubDataStore && usageType !== "Revived") {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (nodeType === GCNodeType.Other) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Called when a node is used. If the node is not active, log an event indicating object is used when its not active.
|
|
127
|
+
*/
|
|
128
|
+
public nodeUsed(nodeUsageProps: INodeUsageProps) {
|
|
129
|
+
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
130
|
+
// logging as nothing interesting would have happened worth logging.
|
|
131
|
+
// If the node is not unreferenced, skip logging.
|
|
132
|
+
const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
|
|
133
|
+
if (!nodeStateTracker || nodeUsageProps.currentReferenceTimestampMs === undefined) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// We log these events once per event per node. A unique id is generated by joining node state (inactive / sweep ready),
|
|
138
|
+
// node's id and usage (loaded / changed / revived).
|
|
139
|
+
const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
|
|
140
|
+
const nodeType = this.getNodeType(nodeUsageProps.id);
|
|
141
|
+
if (
|
|
142
|
+
!this.shouldLogNonActiveEvent(
|
|
143
|
+
nodeUsageProps.id,
|
|
144
|
+
nodeType,
|
|
145
|
+
nodeUsageProps.usageType,
|
|
146
|
+
nodeStateTracker,
|
|
147
|
+
uniqueEventId,
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add the unique event id so that we don't generate a log for this event again in this session..
|
|
154
|
+
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
155
|
+
|
|
156
|
+
const state = nodeStateTracker.state;
|
|
157
|
+
const { usageType, currentReferenceTimestampMs, packagePath, id, fromId, ...propsToLog } =
|
|
158
|
+
nodeUsageProps;
|
|
159
|
+
const eventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
|
|
160
|
+
id: tagAsCodeArtifact(id),
|
|
161
|
+
type: nodeType,
|
|
162
|
+
unrefTime: nodeStateTracker.unreferencedTimestampMs,
|
|
163
|
+
age:
|
|
164
|
+
nodeUsageProps.currentReferenceTimestampMs -
|
|
165
|
+
nodeStateTracker.unreferencedTimestampMs,
|
|
166
|
+
timeout:
|
|
167
|
+
state === UnreferencedState.Inactive
|
|
168
|
+
? this.configs.inactiveTimeoutMs
|
|
169
|
+
: this.configs.sweepTimeoutMs,
|
|
170
|
+
fromId: fromId ? tagAsCodeArtifact(fromId) : undefined,
|
|
171
|
+
...propsToLog,
|
|
172
|
+
...this.createContainerMetadata,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// This will log the following events:
|
|
176
|
+
// GC_Tombstone_DataStore_Revived, GC_Tombstone_SubDataStore_Revived, GC_Tombstone_Blob_Revived
|
|
177
|
+
if (nodeUsageProps.usageType === "Revived" && nodeUsageProps.isTombstoned) {
|
|
178
|
+
sendGCUnexpectedUsageEvent(
|
|
179
|
+
this.mc,
|
|
180
|
+
{
|
|
181
|
+
eventName: `GC_Tombstone_${nodeType}_Revived`,
|
|
182
|
+
category: "generic",
|
|
183
|
+
url: tagAsCodeArtifact(id),
|
|
184
|
+
gcTombstoneEnforcementAllowed: this.gcTombstoneEnforcementAllowed,
|
|
185
|
+
},
|
|
186
|
+
undefined /* packagePath */,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
191
|
+
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
192
|
+
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
193
|
+
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
194
|
+
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
195
|
+
if (this.isSummarizerClient) {
|
|
196
|
+
this.pendingEventsQueue.push({
|
|
197
|
+
...eventProps,
|
|
198
|
+
usageType: nodeUsageProps.usageType,
|
|
199
|
+
state,
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
203
|
+
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
204
|
+
// Events generated:
|
|
205
|
+
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
206
|
+
if (nodeUsageProps.usageType === "Loaded") {
|
|
207
|
+
const { id: taggedId, fromId: taggedFromId, ...otherProps } = eventProps;
|
|
208
|
+
const event = {
|
|
209
|
+
eventName: `${state}Object_${nodeUsageProps.usageType}`,
|
|
210
|
+
pkg: packagePathToTelemetryProperty(nodeUsageProps.packagePath),
|
|
211
|
+
stack: generateStack(),
|
|
212
|
+
id: taggedId,
|
|
213
|
+
fromId: taggedFromId,
|
|
214
|
+
details: JSON.stringify({
|
|
215
|
+
...otherProps,
|
|
216
|
+
}),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
220
|
+
// detecting something wrong with GC either from the partner or from the runtime itself.
|
|
221
|
+
if (state === UnreferencedState.Inactive) {
|
|
222
|
+
this.mc.logger.sendTelemetryEvent(event);
|
|
223
|
+
} else {
|
|
224
|
+
this.mc.logger.sendErrorEvent(event);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.
|
|
232
|
+
* The principle is that every new reference or outbound route must be notified to GC via the
|
|
233
|
+
* addedOutboundReference method. It it hasn't, its a bug and we want to identify these scenarios.
|
|
234
|
+
*
|
|
235
|
+
* In more simple terms:
|
|
236
|
+
* Missing Explicit References = Current References - Previous References - Explicitly Added References;
|
|
237
|
+
*
|
|
238
|
+
* @param currentGCData - The GC data (reference graph) from the current GC run.
|
|
239
|
+
* @param previousGCData - The GC data (reference graph) from the previous GC run.
|
|
240
|
+
* @param explicitReferences - New references added explicity between the previous and the current run.
|
|
241
|
+
*/
|
|
242
|
+
public logIfMissingExplicitReferences(
|
|
243
|
+
currentGCData: IGarbageCollectionData,
|
|
244
|
+
previousGCData: IGarbageCollectionData,
|
|
245
|
+
explicitReferences: Map<string, string[]>,
|
|
246
|
+
logger: ITelemetryLogger,
|
|
247
|
+
) {
|
|
248
|
+
for (const [nodeId, currentOutboundRoutes] of Object.entries(currentGCData.gcNodes)) {
|
|
249
|
+
const previousRoutes = previousGCData.gcNodes[nodeId] ?? [];
|
|
250
|
+
const explicitRoutes = explicitReferences.get(nodeId) ?? [];
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
|
|
254
|
+
* explicit references should be added to missing explicit routes list.
|
|
255
|
+
* 2. Only include data store and blob routes since GC only works for these two.
|
|
256
|
+
* Note: Due to a bug with de-duped blobs, only adding data store routes for now.
|
|
257
|
+
* 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
|
|
258
|
+
* explicit routes to them.
|
|
259
|
+
*/
|
|
260
|
+
const missingExplicitRoutes: string[] = [];
|
|
261
|
+
for (const route of currentOutboundRoutes) {
|
|
262
|
+
const nodeType = this.getNodeType(route);
|
|
263
|
+
if (
|
|
264
|
+
(nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&
|
|
265
|
+
!nodeId.startsWith(route) &&
|
|
266
|
+
!previousRoutes.includes(route) &&
|
|
267
|
+
!explicitRoutes.includes(route)
|
|
268
|
+
) {
|
|
269
|
+
missingExplicitRoutes.push(route);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (missingExplicitRoutes.length > 0) {
|
|
274
|
+
logger.sendErrorEvent({
|
|
275
|
+
eventName: "gcUnknownOutboundReferences",
|
|
276
|
+
id: tagAsCodeArtifact(nodeId),
|
|
277
|
+
routes: tagAsCodeArtifact(JSON.stringify(missingExplicitRoutes)),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Log events that are pending in pendingEventsQueue. This is called after GC runs in the summarizer client
|
|
285
|
+
* so that the state of an unreferenced node is updated.
|
|
286
|
+
*/
|
|
287
|
+
public async logPendingEvents(logger: ITelemetryLogger) {
|
|
288
|
+
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
289
|
+
// summary time they are then logged.
|
|
290
|
+
// Events generated:
|
|
291
|
+
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
292
|
+
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
293
|
+
for (const eventProps of this.pendingEventsQueue) {
|
|
294
|
+
const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
295
|
+
/**
|
|
296
|
+
* Revived event is logged only if the node is active. If the node is not active, the reference to it was
|
|
297
|
+
* from another unreferenced node and this scenario is not interesting to log.
|
|
298
|
+
* Loaded and Changed events are logged only if the node is not active. If the node is active, it was
|
|
299
|
+
* revived and a Revived event will be logged for it.
|
|
300
|
+
*/
|
|
301
|
+
const nodeStateTracker = this.getNodeStateTracker(eventProps.id.value);
|
|
302
|
+
const active =
|
|
303
|
+
nodeStateTracker === undefined ||
|
|
304
|
+
nodeStateTracker.state === UnreferencedState.Active;
|
|
305
|
+
if ((usageType === "Revived") === active) {
|
|
306
|
+
const pkg = await this.getNodePackagePath(eventProps.id.value);
|
|
307
|
+
const fromPkg = eventProps.fromId
|
|
308
|
+
? await this.getNodePackagePath(eventProps.fromId.value)
|
|
309
|
+
: undefined;
|
|
310
|
+
const event = {
|
|
311
|
+
eventName: `${state}Object_${usageType}`,
|
|
312
|
+
details: JSON.stringify({
|
|
313
|
+
...propsToLog,
|
|
314
|
+
}),
|
|
315
|
+
id,
|
|
316
|
+
fromId,
|
|
317
|
+
pkg: pkg ? tagAsCodeArtifact(pkg.join("/")) : undefined,
|
|
318
|
+
fromPkg: fromPkg ? tagAsCodeArtifact(fromPkg.join("/")) : undefined,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
if (state === UnreferencedState.Inactive) {
|
|
322
|
+
logger.sendTelemetryEvent(event);
|
|
323
|
+
} else {
|
|
324
|
+
logger.sendErrorEvent(event);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
this.pendingEventsQueue = [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* For nodes that are ready to sweep, log an event for now. Until we start running sweep which deletes objects,
|
|
333
|
+
* this will give us a view into how much deleted content a container has.
|
|
334
|
+
*/
|
|
335
|
+
public logSweepEvents(
|
|
336
|
+
logger: ITelemetryLogger,
|
|
337
|
+
currentReferenceTimestampMs: number,
|
|
338
|
+
unreferencedNodesState: Map<string, UnreferencedStateTracker>,
|
|
339
|
+
completedGCRuns: number,
|
|
340
|
+
lastSummaryTime?: number,
|
|
341
|
+
) {
|
|
342
|
+
if (
|
|
343
|
+
this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
344
|
+
this.configs.sweepTimeoutMs === undefined
|
|
345
|
+
) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const deletedNodeIds: string[] = [];
|
|
350
|
+
for (const [nodeId, nodeStateTracker] of unreferencedNodesState) {
|
|
351
|
+
if (nodeStateTracker.state !== UnreferencedState.SweepReady) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const nodeType = this.getNodeType(nodeId);
|
|
356
|
+
if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Log deleted event for each node only once to reduce noise in telemetry.
|
|
361
|
+
const uniqueEventId = `Deleted-${nodeId}`;
|
|
362
|
+
if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
366
|
+
deletedNodeIds.push(nodeId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (deletedNodeIds.length > 0) {
|
|
370
|
+
logger.sendTelemetryEvent({
|
|
371
|
+
eventName: "GC_SweepReadyObjects_Delete",
|
|
372
|
+
details: JSON.stringify({
|
|
373
|
+
timeout: this.configs.sweepTimeoutMs,
|
|
374
|
+
completedGCRuns,
|
|
375
|
+
lastSummaryTime,
|
|
376
|
+
...this.createContainerMetadata,
|
|
377
|
+
}),
|
|
378
|
+
id: tagAsCodeArtifact(JSON.stringify(deletedNodeIds)),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Consolidates info / logic for logging when we encounter unexpected usage of GC'd objects. For example, when a
|
|
386
|
+
* tombstoned or deleted object is loaded.
|
|
387
|
+
*/
|
|
388
|
+
export function sendGCUnexpectedUsageEvent(
|
|
389
|
+
mc: MonitoringContext,
|
|
390
|
+
event: ITelemetryGenericEvent & {
|
|
391
|
+
category: "error" | "generic";
|
|
392
|
+
gcTombstoneEnforcementAllowed: boolean | undefined;
|
|
393
|
+
},
|
|
394
|
+
packagePath: readonly string[] | undefined,
|
|
395
|
+
error?: unknown,
|
|
396
|
+
) {
|
|
397
|
+
event.pkg = packagePathToTelemetryProperty(packagePath);
|
|
398
|
+
event.tombstoneFlags = JSON.stringify({
|
|
399
|
+
DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
|
|
400
|
+
ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
401
|
+
ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadKey),
|
|
402
|
+
});
|
|
403
|
+
event.sweepFlags = JSON.stringify({
|
|
404
|
+
EnableSweepFlag: mc.config.getBoolean(runSweepKey),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
mc.logger.sendTelemetryEvent(event, error);
|
|
408
|
+
}
|
package/src/gc/index.ts
CHANGED
|
@@ -39,11 +39,11 @@ export {
|
|
|
39
39
|
cloneGCData,
|
|
40
40
|
concatGarbageCollectionStates,
|
|
41
41
|
getGCDataFromSnapshot,
|
|
42
|
-
sendGCUnexpectedUsageEvent,
|
|
43
42
|
shouldAllowGcTombstoneEnforcement,
|
|
44
43
|
shouldAllowGcSweep,
|
|
45
44
|
trimLeadingAndTrailingSlashes,
|
|
46
45
|
unpackChildNodesGCDetails,
|
|
46
|
+
tagAsCodeArtifact,
|
|
47
47
|
} from "./gcHelpers";
|
|
48
48
|
export { runGarbageCollection } from "./gcReferenceGraphAlgorithm";
|
|
49
49
|
export {
|
|
@@ -57,9 +57,5 @@ export {
|
|
|
57
57
|
GCSummaryStateTracker,
|
|
58
58
|
IGCSummaryTrackingData,
|
|
59
59
|
} from "./gcSummaryStateTracker";
|
|
60
|
-
export {
|
|
61
|
-
skipClosureForXDaysKey,
|
|
62
|
-
closuresMapLocalStorageKey,
|
|
63
|
-
SweepReadyUsageDetectionHandler,
|
|
64
|
-
} from "./gcSweepReadyUsageDetection";
|
|
60
|
+
export { GCTelemetryTracker, sendGCUnexpectedUsageEvent } from "./gcTelemetry";
|
|
65
61
|
export { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
@@ -13,6 +13,7 @@ By default, the runtime is configured with a max batch size of `716800` bytes, w
|
|
|
13
13
|
- [Introduction](#introduction)
|
|
14
14
|
- [Compression](#compression)
|
|
15
15
|
- [Grouped batching](#grouped-batching)
|
|
16
|
+
- [Risks](#risks)
|
|
16
17
|
- [Chunking for compression](#chunking-for-compression)
|
|
17
18
|
- [Disabling in case of emergency](#disabling-in-case-of-emergency)
|
|
18
19
|
- [Example configs](#example-configs)
|
|
@@ -38,6 +39,18 @@ The purpose for enabling grouped batching on top of compression is that regular
|
|
|
38
39
|
|
|
39
40
|
See [below](#how-grouped-batching-works) for an example.
|
|
40
41
|
|
|
42
|
+
### Risks
|
|
43
|
+
|
|
44
|
+
This option is experimental and should not be enabled yet in production. This option should **ONLY** be enabled after observing that 99.9% of your application sessions contains these changes (runtime version "2.0.0-internal.4.1.0" or later). Containers created with this option may not open in future versions of the framework.
|
|
45
|
+
|
|
46
|
+
This option will change a couple of expectations around message structure and runtime layer expectations. Only enable this option after testing
|
|
47
|
+
and verifying that the following expectation changes won't have any effects:
|
|
48
|
+
|
|
49
|
+
- batch messages observed at the runtime layer will not match messages seen at the loader layer (i.e. grouped form at loader layer, ungrouped form at runtime layer)
|
|
50
|
+
- messages within the same batch will have the same sequence number
|
|
51
|
+
- client sequence numbers on batch messages can only be used to order messages with the same sequenceNumber
|
|
52
|
+
- requires all ops to be processed by runtime layer (version "2.0.0-internal.1.2.0" or later https://github.com/microsoft/FluidFramework/pull/11832)
|
|
53
|
+
|
|
41
54
|
## Chunking for compression
|
|
42
55
|
|
|
43
56
|
**Op chunking for compression targets payloads which exceed the max batch size after compression.** So, only payloads which are already compressed. By default, the feature is enabled.
|