@fluidframework/container-runtime 2.0.0-internal.4.3.0 → 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/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +13 -7
- 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/gc/garbageCollection.d.ts +55 -43
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +219 -203
- 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 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +18 -3
- 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 +2 -2
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +42 -22
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +1 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +2 -5
- package/dist/gc/index.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/scheduleManager.js +15 -4
- package/dist/scheduleManager.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/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +13 -7
- 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/gc/garbageCollection.d.ts +55 -43
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +219 -203
- 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 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +16 -2
- 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 +2 -2
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +43 -23
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +1 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +1 -2
- package/lib/gc/index.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/scheduleManager.js +15 -4
- package/lib/scheduleManager.js.map +1 -1
- package/package.json +15 -16
- package/src/blobManager.ts +3 -2
- package/src/containerRuntime.ts +10 -4
- package/src/dataStoreContext.ts +1 -2
- package/src/gc/garbageCollection.ts +274 -257
- package/src/gc/gcConfigs.ts +12 -11
- package/src/gc/gcDefinitions.ts +2 -0
- package/src/gc/gcHelpers.ts +20 -2
- package/src/gc/gcSummaryStateTracker.ts +19 -7
- package/src/gc/gcTelemetry.ts +52 -37
- package/src/gc/index.ts +1 -5
- package/src/packageVersion.ts +1 -1
- package/src/scheduleManager.ts +19 -7
- 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/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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
IGarbageCollectionData,
|
|
13
13
|
IGarbageCollectionDetailsBase,
|
|
14
14
|
} from "@fluidframework/runtime-definitions";
|
|
15
|
+
import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
15
16
|
import { GCFeatureMatrix, GCVersion, IGCMetadata } from "./gcDefinitions";
|
|
16
17
|
import {
|
|
17
18
|
IGarbageCollectionNodeData,
|
|
@@ -151,12 +152,19 @@ export function concatGarbageCollectionStates(
|
|
|
151
152
|
/**
|
|
152
153
|
* Helper function that clones the GC data.
|
|
153
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.
|
|
154
157
|
* @returns a clone of the given GC data.
|
|
155
158
|
*/
|
|
156
|
-
export function cloneGCData(
|
|
159
|
+
export function cloneGCData(
|
|
160
|
+
gcData: IGarbageCollectionData,
|
|
161
|
+
filter?: (id: string) => boolean,
|
|
162
|
+
): IGarbageCollectionData {
|
|
157
163
|
const clonedGCNodes: { [id: string]: string[] } = {};
|
|
158
164
|
for (const [id, outboundRoutes] of Object.entries(gcData.gcNodes)) {
|
|
159
|
-
|
|
165
|
+
if (filter?.(id) !== true) {
|
|
166
|
+
clonedGCNodes[id] = Array.from(outboundRoutes);
|
|
167
|
+
}
|
|
160
168
|
}
|
|
161
169
|
return {
|
|
162
170
|
gcNodes: clonedGCNodes,
|
|
@@ -296,3 +304,13 @@ export function unpackChildNodesGCDetails(gcDetails: IGarbageCollectionDetailsBa
|
|
|
296
304
|
export function trimLeadingAndTrailingSlashes(str: string) {
|
|
297
305
|
return str.replace(/^\/+|\/+$/g, "");
|
|
298
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
|
}
|
package/src/gc/gcTelemetry.ts
CHANGED
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
import { ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
|
|
8
8
|
import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
|
|
9
|
-
import {
|
|
10
|
-
generateStack,
|
|
11
|
-
MonitoringContext,
|
|
12
|
-
TelemetryDataTag,
|
|
13
|
-
} from "@fluidframework/telemetry-utils";
|
|
9
|
+
import { generateStack, MonitoringContext } from "@fluidframework/telemetry-utils";
|
|
14
10
|
import { ICreateContainerMetadata } from "../summary";
|
|
15
11
|
import {
|
|
16
12
|
disableSweepLogKey,
|
|
@@ -23,6 +19,7 @@ import {
|
|
|
23
19
|
runSweepKey,
|
|
24
20
|
} from "./gcDefinitions";
|
|
25
21
|
import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
22
|
+
import { tagAsCodeArtifact } from "./gcHelpers";
|
|
26
23
|
|
|
27
24
|
type NodeUsageType = "Changed" | "Loaded" | "Revived";
|
|
28
25
|
|
|
@@ -32,25 +29,32 @@ interface ICommonProps {
|
|
|
32
29
|
completedGCRuns: number;
|
|
33
30
|
isTombstoned: boolean;
|
|
34
31
|
lastSummaryTime?: number;
|
|
35
|
-
fromId?: string;
|
|
36
32
|
viaHandle?: boolean;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
/** The event that is logged when unreferenced node is used after a certain time. */
|
|
40
36
|
interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps {
|
|
41
37
|
state: UnreferencedState;
|
|
42
|
-
id:
|
|
38
|
+
id: {
|
|
39
|
+
value: string;
|
|
40
|
+
tag: string;
|
|
41
|
+
};
|
|
43
42
|
type: GCNodeType;
|
|
44
43
|
unrefTime: number;
|
|
45
44
|
age: number;
|
|
46
45
|
timeout?: number;
|
|
46
|
+
fromId?: {
|
|
47
|
+
value: string;
|
|
48
|
+
tag: string;
|
|
49
|
+
};
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/** Properties passed to nodeUsed function when a node is used. */
|
|
50
53
|
interface INodeUsageProps extends ICommonProps {
|
|
51
|
-
|
|
54
|
+
id: string;
|
|
52
55
|
currentReferenceTimestampMs: number | undefined;
|
|
53
56
|
packagePath: readonly string[] | undefined;
|
|
57
|
+
fromId?: string;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/**
|
|
@@ -125,18 +129,18 @@ export class GCTelemetryTracker {
|
|
|
125
129
|
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
126
130
|
// logging as nothing interesting would have happened worth logging.
|
|
127
131
|
// If the node is not unreferenced, skip logging.
|
|
128
|
-
const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.
|
|
132
|
+
const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
|
|
129
133
|
if (!nodeStateTracker || nodeUsageProps.currentReferenceTimestampMs === undefined) {
|
|
130
134
|
return;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
// We log these events once per event per node. A unique id is generated by joining node state (inactive / sweep ready),
|
|
134
138
|
// node's id and usage (loaded / changed / revived).
|
|
135
|
-
const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.
|
|
136
|
-
const nodeType = this.getNodeType(nodeUsageProps.
|
|
139
|
+
const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
|
|
140
|
+
const nodeType = this.getNodeType(nodeUsageProps.id);
|
|
137
141
|
if (
|
|
138
142
|
!this.shouldLogNonActiveEvent(
|
|
139
|
-
nodeUsageProps.
|
|
143
|
+
nodeUsageProps.id,
|
|
140
144
|
nodeType,
|
|
141
145
|
nodeUsageProps.usageType,
|
|
142
146
|
nodeStateTracker,
|
|
@@ -150,10 +154,10 @@ export class GCTelemetryTracker {
|
|
|
150
154
|
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
151
155
|
|
|
152
156
|
const state = nodeStateTracker.state;
|
|
153
|
-
const { usageType, currentReferenceTimestampMs, packagePath, ...propsToLog } =
|
|
157
|
+
const { usageType, currentReferenceTimestampMs, packagePath, id, fromId, ...propsToLog } =
|
|
154
158
|
nodeUsageProps;
|
|
155
159
|
const eventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
|
|
156
|
-
id:
|
|
160
|
+
id: tagAsCodeArtifact(id),
|
|
157
161
|
type: nodeType,
|
|
158
162
|
unrefTime: nodeStateTracker.unreferencedTimestampMs,
|
|
159
163
|
age:
|
|
@@ -163,6 +167,7 @@ export class GCTelemetryTracker {
|
|
|
163
167
|
state === UnreferencedState.Inactive
|
|
164
168
|
? this.configs.inactiveTimeoutMs
|
|
165
169
|
: this.configs.sweepTimeoutMs,
|
|
170
|
+
fromId: fromId ? tagAsCodeArtifact(fromId) : undefined,
|
|
166
171
|
...propsToLog,
|
|
167
172
|
...this.createContainerMetadata,
|
|
168
173
|
};
|
|
@@ -175,7 +180,7 @@ export class GCTelemetryTracker {
|
|
|
175
180
|
{
|
|
176
181
|
eventName: `GC_Tombstone_${nodeType}_Revived`,
|
|
177
182
|
category: "generic",
|
|
178
|
-
url:
|
|
183
|
+
url: tagAsCodeArtifact(id),
|
|
179
184
|
gcTombstoneEnforcementAllowed: this.gcTombstoneEnforcementAllowed,
|
|
180
185
|
},
|
|
181
186
|
undefined /* packagePath */,
|
|
@@ -199,11 +204,16 @@ export class GCTelemetryTracker {
|
|
|
199
204
|
// Events generated:
|
|
200
205
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
201
206
|
if (nodeUsageProps.usageType === "Loaded") {
|
|
207
|
+
const { id: taggedId, fromId: taggedFromId, ...otherProps } = eventProps;
|
|
202
208
|
const event = {
|
|
203
209
|
eventName: `${state}Object_${nodeUsageProps.usageType}`,
|
|
204
210
|
pkg: packagePathToTelemetryProperty(nodeUsageProps.packagePath),
|
|
205
211
|
stack: generateStack(),
|
|
206
|
-
|
|
212
|
+
id: taggedId,
|
|
213
|
+
fromId: taggedFromId,
|
|
214
|
+
details: JSON.stringify({
|
|
215
|
+
...otherProps,
|
|
216
|
+
}),
|
|
207
217
|
};
|
|
208
218
|
|
|
209
219
|
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
@@ -263,8 +273,8 @@ export class GCTelemetryTracker {
|
|
|
263
273
|
if (missingExplicitRoutes.length > 0) {
|
|
264
274
|
logger.sendErrorEvent({
|
|
265
275
|
eventName: "gcUnknownOutboundReferences",
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
id: tagAsCodeArtifact(nodeId),
|
|
277
|
+
routes: tagAsCodeArtifact(JSON.stringify(missingExplicitRoutes)),
|
|
268
278
|
});
|
|
269
279
|
}
|
|
270
280
|
}
|
|
@@ -281,31 +291,31 @@ export class GCTelemetryTracker {
|
|
|
281
291
|
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
282
292
|
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
283
293
|
for (const eventProps of this.pendingEventsQueue) {
|
|
284
|
-
const { usageType, state, ...propsToLog } = eventProps;
|
|
294
|
+
const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
285
295
|
/**
|
|
286
296
|
* Revived event is logged only if the node is active. If the node is not active, the reference to it was
|
|
287
297
|
* from another unreferenced node and this scenario is not interesting to log.
|
|
288
298
|
* Loaded and Changed events are logged only if the node is not active. If the node is active, it was
|
|
289
299
|
* revived and a Revived event will be logged for it.
|
|
290
300
|
*/
|
|
291
|
-
const nodeStateTracker = this.getNodeStateTracker(eventProps.id);
|
|
301
|
+
const nodeStateTracker = this.getNodeStateTracker(eventProps.id.value);
|
|
292
302
|
const active =
|
|
293
303
|
nodeStateTracker === undefined ||
|
|
294
304
|
nodeStateTracker.state === UnreferencedState.Active;
|
|
295
305
|
if ((usageType === "Revived") === active) {
|
|
296
|
-
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
306
|
+
const pkg = await this.getNodePackagePath(eventProps.id.value);
|
|
297
307
|
const fromPkg = eventProps.fromId
|
|
298
|
-
? await this.getNodePackagePath(eventProps.fromId)
|
|
308
|
+
? await this.getNodePackagePath(eventProps.fromId.value)
|
|
299
309
|
: undefined;
|
|
300
310
|
const event = {
|
|
301
|
-
...propsToLog,
|
|
302
311
|
eventName: `${state}Object_${usageType}`,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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,
|
|
309
319
|
};
|
|
310
320
|
|
|
311
321
|
if (state === UnreferencedState.Inactive) {
|
|
@@ -336,6 +346,7 @@ export class GCTelemetryTracker {
|
|
|
336
346
|
return;
|
|
337
347
|
}
|
|
338
348
|
|
|
349
|
+
const deletedNodeIds: string[] = [];
|
|
339
350
|
for (const [nodeId, nodeStateTracker] of unreferencedNodesState) {
|
|
340
351
|
if (nodeStateTracker.state !== UnreferencedState.SweepReady) {
|
|
341
352
|
return;
|
|
@@ -352,15 +363,19 @@ export class GCTelemetryTracker {
|
|
|
352
363
|
return;
|
|
353
364
|
}
|
|
354
365
|
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
366
|
+
deletedNodeIds.push(nodeId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (deletedNodeIds.length > 0) {
|
|
355
370
|
logger.sendTelemetryEvent({
|
|
356
|
-
eventName: "
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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)),
|
|
364
379
|
});
|
|
365
380
|
}
|
|
366
381
|
}
|
package/src/gc/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ export {
|
|
|
43
43
|
shouldAllowGcSweep,
|
|
44
44
|
trimLeadingAndTrailingSlashes,
|
|
45
45
|
unpackChildNodesGCDetails,
|
|
46
|
+
tagAsCodeArtifact,
|
|
46
47
|
} from "./gcHelpers";
|
|
47
48
|
export { runGarbageCollection } from "./gcReferenceGraphAlgorithm";
|
|
48
49
|
export {
|
|
@@ -56,10 +57,5 @@ export {
|
|
|
56
57
|
GCSummaryStateTracker,
|
|
57
58
|
IGCSummaryTrackingData,
|
|
58
59
|
} from "./gcSummaryStateTracker";
|
|
59
|
-
export {
|
|
60
|
-
skipClosureForXDaysKey,
|
|
61
|
-
closuresMapLocalStorageKey,
|
|
62
|
-
SweepReadyUsageDetectionHandler,
|
|
63
|
-
} from "./gcSweepReadyUsageDetection";
|
|
64
60
|
export { GCTelemetryTracker, sendGCUnexpectedUsageEvent } from "./gcTelemetry";
|
|
65
61
|
export { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
package/src/packageVersion.ts
CHANGED
package/src/scheduleManager.ts
CHANGED
|
@@ -147,14 +147,14 @@ class ScheduleManagerCore {
|
|
|
147
147
|
// We are intentionally directly listening to the "op" to inspect system ops as well.
|
|
148
148
|
// If we do not observe system ops, we are likely to hit 0x296 assert when system ops
|
|
149
149
|
// precedes start of incomplete batch.
|
|
150
|
-
this.deltaManager.on("op", (message) => this.afterOpProcessing(message
|
|
150
|
+
this.deltaManager.on("op", (message) => this.afterOpProcessing(message));
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
154
|
* The only public function in this class - called when we processed an op,
|
|
155
155
|
* to make decision if op processing should be paused or not after that.
|
|
156
156
|
*/
|
|
157
|
-
public afterOpProcessing(
|
|
157
|
+
public afterOpProcessing(message: ISequencedDocumentMessage) {
|
|
158
158
|
assert(
|
|
159
159
|
!this.localPaused,
|
|
160
160
|
0x294 /* "can't have op processing paused if we are processing an op" */,
|
|
@@ -177,12 +177,24 @@ class ScheduleManagerCore {
|
|
|
177
177
|
|
|
178
178
|
// do we have incomplete batch to worry about?
|
|
179
179
|
if (this.pauseSequenceNumber !== undefined) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
if (message.sequenceNumber >= this.pauseSequenceNumber) {
|
|
181
|
+
throw DataProcessingError.create(
|
|
182
|
+
// Former assert 0x296
|
|
183
|
+
"Incomplete batch",
|
|
184
|
+
"ScheduleManager",
|
|
185
|
+
message,
|
|
186
|
+
{
|
|
187
|
+
type: message.type,
|
|
188
|
+
contentType: typeof message.contents,
|
|
189
|
+
batch: message.metadata?.batch,
|
|
190
|
+
compression: message.compression,
|
|
191
|
+
pauseSeqNum: this.pauseSequenceNumber,
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
184
196
|
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
185
|
-
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
197
|
+
if (message.sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
186
198
|
this.pauseQueue();
|
|
187
199
|
}
|
|
188
200
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import { ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
6
|
-
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
7
|
-
import { IFluidErrorBase, LoggingError, MonitoringContext } from "@fluidframework/telemetry-utils";
|
|
8
|
-
/**
|
|
9
|
-
* Feature Gate Key -
|
|
10
|
-
* How many days between closing the container from this error (avoids locking user out of their file altogether)
|
|
11
|
-
*/
|
|
12
|
-
export declare const skipClosureForXDaysKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
|
|
13
|
-
/**
|
|
14
|
-
* LocalStorage key (NOT via feature gate / monitoring context)
|
|
15
|
-
* A map from docId to info about the last time we closed due to this error
|
|
16
|
-
*/
|
|
17
|
-
export declare const closuresMapLocalStorageKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
|
|
18
|
-
/**
|
|
19
|
-
* Error class raised when a SweepReady object is used, indicating a bug in how
|
|
20
|
-
* references are managed in the container by the application, or a bug in how
|
|
21
|
-
* GC tracks those references.
|
|
22
|
-
*
|
|
23
|
-
* There's a chance for false positives when this error is raised by an Interactive Container,
|
|
24
|
-
* since only the Summarizer has the latest truth about unreferenced node tracking
|
|
25
|
-
*/
|
|
26
|
-
export declare class SweepReadyUsageError extends LoggingError implements IFluidErrorBase {
|
|
27
|
-
/** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
|
|
28
|
-
errorType: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* This class encapsulates the logic around what to do when a SweepReady object is used.
|
|
32
|
-
* There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:
|
|
33
|
-
* - Closing the interactive container when either the interactive or summarizer client detects this kind of violation
|
|
34
|
-
* (via sweepReadyUsageDetectionSetting above)
|
|
35
|
-
* - Throttling the frequency of these crashes via a "Skip Closure Period" per container per device
|
|
36
|
-
* (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)
|
|
37
|
-
*/
|
|
38
|
-
export declare class SweepReadyUsageDetectionHandler {
|
|
39
|
-
private readonly uniqueContainerKey;
|
|
40
|
-
private readonly mc;
|
|
41
|
-
private readonly closeFn;
|
|
42
|
-
private readonly localStorage;
|
|
43
|
-
constructor(uniqueContainerKey: string, mc: MonitoringContext, closeFn: (error?: ICriticalContainerError) => void, localStorageOverride?: Pick<Storage, "getItem" | "setItem">);
|
|
44
|
-
/**
|
|
45
|
-
* If SweepReady Usage Detection is enabled, close the interactive container.
|
|
46
|
-
* If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
|
|
47
|
-
*
|
|
48
|
-
* Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
49
|
-
* and errors will arise elsewhere in the runtime
|
|
50
|
-
*/
|
|
51
|
-
usageDetectedInInteractiveClient(errorProps: ITelemetryProperties): void;
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=gcSweepReadyUsageDetection.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gcSweepReadyUsageDetection.d.ts","sourceRoot":"","sources":["../../src/gc/gcSweepReadyUsageDetection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAEN,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,MAAM,iCAAiC,CAAC;AAGzC;;;GAGG;AACH,eAAO,MAAM,sBAAsB,iFAC4C,CAAC;AAEhF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,sEAC6B,CAAC;AAqBrE;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,YAAa,YAAW,eAAe;IAChF,oHAAoH;IAC7G,SAAS,EAAE,MAAM,CAAiD;CACzE;AAED;;;;;;;GAOG;AACH,qBAAa,+BAA+B;IAI1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALzB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;gBAGlD,kBAAkB,EAAE,MAAM,EAC1B,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,uBAAuB,KAAK,IAAI,EACnE,oBAAoB,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAc5D;;;;;;OAMG;IACI,gCAAgC,CAAC,UAAU,EAAE,oBAAoB;CA+CxE"}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.SweepReadyUsageDetectionHandler = exports.SweepReadyUsageError = exports.closuresMapLocalStorageKey = exports.skipClosureForXDaysKey = void 0;
|
|
8
|
-
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
9
|
-
const gcDefinitions_1 = require("./gcDefinitions");
|
|
10
|
-
/**
|
|
11
|
-
* Feature Gate Key -
|
|
12
|
-
* How many days between closing the container from this error (avoids locking user out of their file altogether)
|
|
13
|
-
*/
|
|
14
|
-
exports.skipClosureForXDaysKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
|
|
15
|
-
/**
|
|
16
|
-
* LocalStorage key (NOT via feature gate / monitoring context)
|
|
17
|
-
* A map from docId to info about the last time we closed due to this error
|
|
18
|
-
*/
|
|
19
|
-
exports.closuresMapLocalStorageKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
|
|
20
|
-
/**
|
|
21
|
-
* Feature gate key to enable closing the container if SweepReady objects are used.
|
|
22
|
-
* Value should contain keywords "interactiveClient" and/or "summarizer" to enable detection in each container type
|
|
23
|
-
*/
|
|
24
|
-
const sweepReadyUsageDetectionSetting = {
|
|
25
|
-
read(config) {
|
|
26
|
-
const sweepReadyUsageDetectionKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection";
|
|
27
|
-
const value = config.getString(sweepReadyUsageDetectionKey);
|
|
28
|
-
if (value === undefined) {
|
|
29
|
-
return { interactiveClient: false, summarizer: false };
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
interactiveClient: value.includes("interactiveClient"),
|
|
33
|
-
summarizer: value.includes("summarizer"),
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Error class raised when a SweepReady object is used, indicating a bug in how
|
|
39
|
-
* references are managed in the container by the application, or a bug in how
|
|
40
|
-
* GC tracks those references.
|
|
41
|
-
*
|
|
42
|
-
* There's a chance for false positives when this error is raised by an Interactive Container,
|
|
43
|
-
* since only the Summarizer has the latest truth about unreferenced node tracking
|
|
44
|
-
*/
|
|
45
|
-
class SweepReadyUsageError extends telemetry_utils_1.LoggingError {
|
|
46
|
-
constructor() {
|
|
47
|
-
super(...arguments);
|
|
48
|
-
/** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
|
|
49
|
-
this.errorType = "unreferencedObjectUsedAfterGarbageCollected";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
exports.SweepReadyUsageError = SweepReadyUsageError;
|
|
53
|
-
/**
|
|
54
|
-
* This class encapsulates the logic around what to do when a SweepReady object is used.
|
|
55
|
-
* There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:
|
|
56
|
-
* - Closing the interactive container when either the interactive or summarizer client detects this kind of violation
|
|
57
|
-
* (via sweepReadyUsageDetectionSetting above)
|
|
58
|
-
* - Throttling the frequency of these crashes via a "Skip Closure Period" per container per device
|
|
59
|
-
* (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)
|
|
60
|
-
*/
|
|
61
|
-
class SweepReadyUsageDetectionHandler {
|
|
62
|
-
constructor(uniqueContainerKey, mc, closeFn, localStorageOverride) {
|
|
63
|
-
var _a;
|
|
64
|
-
this.uniqueContainerKey = uniqueContainerKey;
|
|
65
|
-
this.mc = mc;
|
|
66
|
-
this.closeFn = closeFn;
|
|
67
|
-
const noopStorage = { getItem: () => null, setItem: () => { } };
|
|
68
|
-
// localStorage is not defined in Node environment, so fall back to noopStorage if needed.
|
|
69
|
-
this.localStorage = (_a = localStorageOverride !== null && localStorageOverride !== void 0 ? localStorageOverride : globalThis.localStorage) !== null && _a !== void 0 ? _a : noopStorage;
|
|
70
|
-
if (this.localStorage === noopStorage) {
|
|
71
|
-
// This means the Skip Closure Period logic will not work.
|
|
72
|
-
this.mc.logger.sendTelemetryEvent({
|
|
73
|
-
eventName: "SweepReadyUsageDetectionHandlerNoopStorage",
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* If SweepReady Usage Detection is enabled, close the interactive container.
|
|
79
|
-
* If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
|
|
80
|
-
*
|
|
81
|
-
* Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
82
|
-
* and errors will arise elsewhere in the runtime
|
|
83
|
-
*/
|
|
84
|
-
usageDetectedInInteractiveClient(errorProps) {
|
|
85
|
-
var _a;
|
|
86
|
-
if (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Default stance is we close every time - this reflects the severity of SweepReady Object Usage.
|
|
90
|
-
// However, we may choose to "throttle" the closures by setting the SkipClosureForXDays setting,
|
|
91
|
-
// which will only allow the container to close once during that period, to avoid locking users out.
|
|
92
|
-
let shouldClose = true;
|
|
93
|
-
let pastClosuresMap = {};
|
|
94
|
-
let lastCloseTime;
|
|
95
|
-
const skipClosureForXDays = this.mc.config.getNumber(exports.skipClosureForXDaysKey);
|
|
96
|
-
if (skipClosureForXDays !== undefined) {
|
|
97
|
-
// Read pastClosuresMap from localStorage then extract the lastCloseTime from the map
|
|
98
|
-
try {
|
|
99
|
-
const rawValue = this.localStorage.getItem(exports.closuresMapLocalStorageKey);
|
|
100
|
-
const parsedValue = rawValue === null ? {} : JSON.parse(rawValue);
|
|
101
|
-
if (typeof parsedValue === "object") {
|
|
102
|
-
pastClosuresMap = parsedValue;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (e) { }
|
|
106
|
-
lastCloseTime = (_a = pastClosuresMap[this.uniqueContainerKey]) === null || _a === void 0 ? void 0 : _a.lastCloseTime;
|
|
107
|
-
// Don't close if we did already within the Skip Closure Period
|
|
108
|
-
if (lastCloseTime !== undefined &&
|
|
109
|
-
Date.now() < lastCloseTime + skipClosureForXDays * gcDefinitions_1.oneDayMs) {
|
|
110
|
-
shouldClose = false;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const error = new SweepReadyUsageError("SweepReady object used in Non-Summarizer Client", {
|
|
114
|
-
errorDetails: JSON.stringify(Object.assign(Object.assign({}, errorProps), { lastCloseTime, skipClosureForXDays })),
|
|
115
|
-
});
|
|
116
|
-
if (shouldClose) {
|
|
117
|
-
// Update closures map in localStorage before closing
|
|
118
|
-
// Note there is a race condition between different tabs updating localStorage and overwriting
|
|
119
|
-
// each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck
|
|
120
|
-
pastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };
|
|
121
|
-
this.localStorage.setItem(exports.closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));
|
|
122
|
-
this.closeFn(error);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
this.mc.logger.sendErrorEvent({ eventName: "SweepReadyObject_UsageAllowed" }, error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
exports.SweepReadyUsageDetectionHandler = SweepReadyUsageDetectionHandler;
|
|
130
|
-
//# sourceMappingURL=gcSweepReadyUsageDetection.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gcSweepReadyUsageDetection.js","sourceRoot":"","sources":["../../src/gc/gcSweepReadyUsageDetection.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryProperties } from \"@fluidframework/common-definitions\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport {\n\tIConfigProvider,\n\tIFluidErrorBase,\n\tLoggingError,\n\tMonitoringContext,\n} from \"@fluidframework/telemetry-utils\";\nimport { oneDayMs } from \"./gcDefinitions\";\n\n/**\n * Feature Gate Key -\n * How many days between closing the container from this error (avoids locking user out of their file altogether)\n */\nexport const skipClosureForXDaysKey =\n\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays\";\n\n/**\n * LocalStorage key (NOT via feature gate / monitoring context)\n * A map from docId to info about the last time we closed due to this error\n */\nexport const closuresMapLocalStorageKey =\n\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures\";\n\n/**\n * Feature gate key to enable closing the container if SweepReady objects are used.\n * Value should contain keywords \"interactiveClient\" and/or \"summarizer\" to enable detection in each container type\n */\nconst sweepReadyUsageDetectionSetting = {\n\tread(config: IConfigProvider) {\n\t\tconst sweepReadyUsageDetectionKey =\n\t\t\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection\";\n\t\tconst value = config.getString(sweepReadyUsageDetectionKey);\n\t\tif (value === undefined) {\n\t\t\treturn { interactiveClient: false, summarizer: false };\n\t\t}\n\t\treturn {\n\t\t\tinteractiveClient: value.includes(\"interactiveClient\"),\n\t\t\tsummarizer: value.includes(\"summarizer\"),\n\t\t};\n\t},\n};\n\n/**\n * Error class raised when a SweepReady object is used, indicating a bug in how\n * references are managed in the container by the application, or a bug in how\n * GC tracks those references.\n *\n * There's a chance for false positives when this error is raised by an Interactive Container,\n * since only the Summarizer has the latest truth about unreferenced node tracking\n */\nexport class SweepReadyUsageError extends LoggingError implements IFluidErrorBase {\n\t/** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */\n\tpublic errorType: string = \"unreferencedObjectUsedAfterGarbageCollected\";\n}\n\n/**\n * This class encapsulates the logic around what to do when a SweepReady object is used.\n * There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:\n * - Closing the interactive container when either the interactive or summarizer client detects this kind of violation\n * (via sweepReadyUsageDetectionSetting above)\n * - Throttling the frequency of these crashes via a \"Skip Closure Period\" per container per device\n * (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)\n */\nexport class SweepReadyUsageDetectionHandler {\n\tprivate readonly localStorage: Pick<Storage, \"getItem\" | \"setItem\">;\n\n\tconstructor(\n\t\tprivate readonly uniqueContainerKey: string,\n\t\tprivate readonly mc: MonitoringContext,\n\t\tprivate readonly closeFn: (error?: ICriticalContainerError) => void,\n\t\tlocalStorageOverride?: Pick<Storage, \"getItem\" | \"setItem\">,\n\t) {\n\t\tconst noopStorage = { getItem: () => null, setItem: () => {} };\n\t\t// localStorage is not defined in Node environment, so fall back to noopStorage if needed.\n\t\tthis.localStorage = localStorageOverride ?? globalThis.localStorage ?? noopStorage;\n\n\t\tif (this.localStorage === noopStorage) {\n\t\t\t// This means the Skip Closure Period logic will not work.\n\t\t\tthis.mc.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"SweepReadyUsageDetectionHandlerNoopStorage\",\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * If SweepReady Usage Detection is enabled, close the interactive container.\n\t * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.\n\t *\n\t * Once Sweep is fully implemented, this will be removed since the objects will be gone\n\t * and errors will arise elsewhere in the runtime\n\t */\n\tpublic usageDetectedInInteractiveClient(errorProps: ITelemetryProperties) {\n\t\tif (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Default stance is we close every time - this reflects the severity of SweepReady Object Usage.\n\t\t// However, we may choose to \"throttle\" the closures by setting the SkipClosureForXDays setting,\n\t\t// which will only allow the container to close once during that period, to avoid locking users out.\n\t\tlet shouldClose: boolean = true;\n\t\tlet pastClosuresMap: Record<string, { lastCloseTime: number } | undefined> = {};\n\t\tlet lastCloseTime: number | undefined;\n\t\tconst skipClosureForXDays = this.mc.config.getNumber(skipClosureForXDaysKey);\n\t\tif (skipClosureForXDays !== undefined) {\n\t\t\t// Read pastClosuresMap from localStorage then extract the lastCloseTime from the map\n\t\t\ttry {\n\t\t\t\tconst rawValue = this.localStorage.getItem(closuresMapLocalStorageKey);\n\t\t\t\tconst parsedValue = rawValue === null ? {} : JSON.parse(rawValue);\n\t\t\t\tif (typeof parsedValue === \"object\") {\n\t\t\t\t\tpastClosuresMap = parsedValue;\n\t\t\t\t}\n\t\t\t} catch (e) {}\n\t\t\tlastCloseTime = pastClosuresMap[this.uniqueContainerKey]?.lastCloseTime;\n\n\t\t\t// Don't close if we did already within the Skip Closure Period\n\t\t\tif (\n\t\t\t\tlastCloseTime !== undefined &&\n\t\t\t\tDate.now() < lastCloseTime + skipClosureForXDays * oneDayMs\n\t\t\t) {\n\t\t\t\tshouldClose = false;\n\t\t\t}\n\t\t}\n\n\t\tconst error = new SweepReadyUsageError(\"SweepReady object used in Non-Summarizer Client\", {\n\t\t\terrorDetails: JSON.stringify({ ...errorProps, lastCloseTime, skipClosureForXDays }),\n\t\t});\n\t\tif (shouldClose) {\n\t\t\t// Update closures map in localStorage before closing\n\t\t\t// Note there is a race condition between different tabs updating localStorage and overwriting\n\t\t\t// each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck\n\t\t\tpastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };\n\t\t\tthis.localStorage.setItem(closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));\n\n\t\t\tthis.closeFn(error);\n\t\t} else {\n\t\t\tthis.mc.logger.sendErrorEvent({ eventName: \"SweepReadyObject_UsageAllowed\" }, error);\n\t\t}\n\t}\n}\n"]}
|