@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
|
@@ -39,7 +39,6 @@ export class OpGroupingManager {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// Need deserializedContent for back-compat
|
|
43
42
|
const deserializedContent = {
|
|
44
43
|
type: OpGroupingManager.groupedBatchOp,
|
|
45
44
|
contents: batch.content.map<IGroupedMessage>((message) => ({
|
|
@@ -56,6 +55,7 @@ export class OpGroupingManager {
|
|
|
56
55
|
localOpMetadata: undefined,
|
|
57
56
|
metadata: undefined,
|
|
58
57
|
referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
|
|
58
|
+
// Need deserializedContent for back-compat
|
|
59
59
|
deserializedContent: deserializedContent as ContainerRuntimeMessage,
|
|
60
60
|
contents: JSON.stringify(deserializedContent),
|
|
61
61
|
},
|
|
@@ -219,8 +219,8 @@ export class Outbox {
|
|
|
219
219
|
return this.params.groupingManager.groupBatch(batch);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
const compressedBatch = this.params.
|
|
223
|
-
this.params.
|
|
222
|
+
const compressedBatch = this.params.compressor.compressBatch(
|
|
223
|
+
this.params.groupingManager.groupBatch(batch),
|
|
224
224
|
);
|
|
225
225
|
|
|
226
226
|
if (this.params.splitter.isBatchChunkingEnabled) {
|
|
@@ -27,40 +27,49 @@ export class RemoteMessageProcessor {
|
|
|
27
27
|
public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
|
|
28
28
|
const result: ISequencedDocumentMessage[] = [];
|
|
29
29
|
|
|
30
|
-
// Ungroup before
|
|
31
|
-
for (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Ungroup the chunked message before decompressing
|
|
45
|
-
for (let ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
|
|
46
|
-
ungroupedMessage,
|
|
47
|
-
)) {
|
|
48
|
-
const decompressionAfterChunking = this.opDecompressor.processMessage(
|
|
49
|
-
ungroupedMessageAfterChunking,
|
|
50
|
-
);
|
|
51
|
-
ungroupedMessageAfterChunking = decompressionAfterChunking.message;
|
|
52
|
-
if (decompressionAfterChunking.state === "Skipped") {
|
|
53
|
-
// After chunking, if the original message was not compressed,
|
|
30
|
+
// Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
|
|
31
|
+
for (const ungroupedMessage of this.opGroupingManager.ungroupOp(copy(remoteMessage))) {
|
|
32
|
+
const message = this.opDecompressor.processMessage(ungroupedMessage).message;
|
|
33
|
+
|
|
34
|
+
for (let ungroupedMessage2 of this.opGroupingManager.ungroupOp(message)) {
|
|
35
|
+
unpackRuntimeMessage(ungroupedMessage2);
|
|
36
|
+
|
|
37
|
+
const chunkProcessingResult =
|
|
38
|
+
this.opSplitter.processRemoteMessage(ungroupedMessage2);
|
|
39
|
+
ungroupedMessage2 = chunkProcessingResult.message;
|
|
40
|
+
if (chunkProcessingResult.state !== "Processed") {
|
|
41
|
+
// If the message is not chunked or if the splitter is still rebuilding the original message,
|
|
54
42
|
// there is no need to continue processing
|
|
55
|
-
result.push(
|
|
43
|
+
result.push(ungroupedMessage2);
|
|
56
44
|
continue;
|
|
57
45
|
}
|
|
58
46
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
// Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
|
|
48
|
+
for (const ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
|
|
49
|
+
ungroupedMessage2,
|
|
50
|
+
)) {
|
|
51
|
+
const decompressionAfterChunking = this.opDecompressor.processMessage(
|
|
52
|
+
ungroupedMessageAfterChunking,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
for (const ungroupedMessageAfterChunking2 of this.opGroupingManager.ungroupOp(
|
|
56
|
+
decompressionAfterChunking.message,
|
|
57
|
+
)) {
|
|
58
|
+
if (decompressionAfterChunking.state === "Skipped") {
|
|
59
|
+
// After chunking, if the original message was not compressed,
|
|
60
|
+
// there is no need to continue processing
|
|
61
|
+
result.push(ungroupedMessageAfterChunking2);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The message needs to be unpacked after chunking + decompression
|
|
66
|
+
unpack(ungroupedMessageAfterChunking2);
|
|
67
|
+
result.push(ungroupedMessageAfterChunking2);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
}
|
|
72
|
+
|
|
64
73
|
return result;
|
|
65
74
|
}
|
|
66
75
|
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -49,10 +49,7 @@ export interface IRuntimeStateHandler {
|
|
|
49
49
|
connected(): boolean;
|
|
50
50
|
clientId(): string | undefined;
|
|
51
51
|
close(error?: ICriticalContainerError): void;
|
|
52
|
-
applyStashedOp: (
|
|
53
|
-
type: ContainerMessageType,
|
|
54
|
-
content: ISequencedDocumentMessage,
|
|
55
|
-
) => Promise<unknown>;
|
|
52
|
+
applyStashedOp: (type: ContainerMessageType, content: unknown) => Promise<unknown>;
|
|
56
53
|
reSubmit(
|
|
57
54
|
type: ContainerMessageType,
|
|
58
55
|
content: any,
|
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
|
}
|
|
@@ -416,14 +416,13 @@ export class OrderedClientElection
|
|
|
416
416
|
change = true;
|
|
417
417
|
}
|
|
418
418
|
if (change) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
419
|
+
this.logger.sendTelemetryEvent({
|
|
420
|
+
eventName: "SummarizerClientElected",
|
|
421
|
+
electedClientId: this._electedClient?.clientId,
|
|
422
|
+
electedParentId: this._electedParent?.clientId,
|
|
423
|
+
electionSequenceNumber: sequenceNumber,
|
|
424
|
+
isSummarizerClient,
|
|
425
|
+
});
|
|
427
426
|
this.emit("election", client, sequenceNumber, prevClient);
|
|
428
427
|
}
|
|
429
428
|
}
|
|
@@ -431,14 +430,12 @@ export class OrderedClientElection
|
|
|
431
430
|
private tryElectingParent(client: ILinkedClient | undefined, sequenceNumber: number): void {
|
|
432
431
|
if (this._electedParent !== client) {
|
|
433
432
|
this._electedParent = client;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
});
|
|
441
|
-
}
|
|
433
|
+
this.logger.sendTelemetryEvent({
|
|
434
|
+
eventName: "SummarizerParentElected",
|
|
435
|
+
electedClientId: this._electedClient?.clientId,
|
|
436
|
+
electedParentId: this._electedParent?.clientId,
|
|
437
|
+
electionSequenceNumber: sequenceNumber,
|
|
438
|
+
});
|
|
442
439
|
this.emit("election", this._electedClient, sequenceNumber, this._electedClient);
|
|
443
440
|
}
|
|
444
441
|
}
|
|
@@ -473,7 +470,7 @@ export class OrderedClientElection
|
|
|
473
470
|
const newClientIsSummarizer = client.client.details.type === summarizerClientType;
|
|
474
471
|
const electedClientIsSummarizer =
|
|
475
472
|
this._electedClient?.client.details.type === summarizerClientType;
|
|
476
|
-
// Note that we allow a summarizer client to
|
|
473
|
+
// Note that we allow a summarizer client to supersede an interactive client as elected client.
|
|
477
474
|
if (
|
|
478
475
|
this._electedClient === undefined ||
|
|
479
476
|
(!electedClientIsSummarizer && newClientIsSummarizer)
|
|
@@ -25,6 +25,7 @@ import { RunningSummarizer } from "./runningSummarizer";
|
|
|
25
25
|
import {
|
|
26
26
|
IConnectableRuntime,
|
|
27
27
|
ISummarizer,
|
|
28
|
+
ISummarizeHeuristicData,
|
|
28
29
|
ISummarizerInternalsProvider,
|
|
29
30
|
ISummarizerRuntime,
|
|
30
31
|
ISummarizingWarning,
|
|
@@ -215,6 +216,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
215
216
|
return stopReason === "parentNotConnected";
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
private _heuristicData: ISummarizeHeuristicData | undefined;
|
|
220
|
+
|
|
218
221
|
/**
|
|
219
222
|
* Put the summarizer in a started state, including creating and initializing the RunningSummarizer.
|
|
220
223
|
* The start request can come either from the SummaryManager (in the auto-summarize case) or from the user
|
|
@@ -252,17 +255,22 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
252
255
|
throw new UsageError("clientId should be defined if connected.");
|
|
253
256
|
}
|
|
254
257
|
|
|
258
|
+
this._heuristicData = new SummarizeHeuristicData(
|
|
259
|
+
this.runtime.deltaManager.lastSequenceNumber,
|
|
260
|
+
{
|
|
261
|
+
/** summary attempt baseline for heuristics */
|
|
262
|
+
refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
|
|
263
|
+
summaryTime: Date.now(),
|
|
264
|
+
} as const,
|
|
265
|
+
);
|
|
266
|
+
|
|
255
267
|
const runningSummarizer = await RunningSummarizer.start(
|
|
256
268
|
this.logger,
|
|
257
269
|
this.summaryCollection.createWatcher(clientId),
|
|
258
270
|
this.configurationGetter(),
|
|
259
271
|
async (...args) => this.internalsProvider.submitSummary(...args), // submitSummaryCallback
|
|
260
272
|
async (...args) => this.internalsProvider.refreshLatestSummaryAck(...args), // refreshLatestSummaryCallback
|
|
261
|
-
|
|
262
|
-
/** summary attempt baseline for heuristics */
|
|
263
|
-
refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
|
|
264
|
-
summaryTime: Date.now(),
|
|
265
|
-
} as const),
|
|
273
|
+
this._heuristicData,
|
|
266
274
|
this.summaryCollection,
|
|
267
275
|
runCoordinator /* cancellationToken */,
|
|
268
276
|
(reason) => runCoordinator.stop(reason) /* stopSummarizerCallback */,
|
|
@@ -361,4 +369,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
361
369
|
}
|
|
362
370
|
return this.runningSummarizer.enqueueSummarize(...args);
|
|
363
371
|
};
|
|
372
|
+
|
|
373
|
+
public recordSummaryAttempt?(summaryRefSeqNum?: number) {
|
|
374
|
+
this._heuristicData?.recordAttempt(summaryRefSeqNum);
|
|
375
|
+
}
|
|
364
376
|
}
|
|
@@ -27,32 +27,31 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
public get opsSinceLastSummary(): number {
|
|
30
|
-
return this.
|
|
30
|
+
return this.numNonRuntimeOpsBefore + this.numRuntimeOpsBefore;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
public numNonRuntimeOps: number = 0;
|
|
34
|
-
public totalOpsSize: number = 0;
|
|
35
33
|
public hasMissingOpData: boolean = false;
|
|
36
34
|
|
|
35
|
+
public totalOpsSize: number = 0;
|
|
37
36
|
/**
|
|
38
37
|
* Cumulative size in bytes of all the ops at the beginning of the summarization attempt.
|
|
39
38
|
* Is used to adjust totalOpsSize appropriately after successful summarization.
|
|
40
39
|
*/
|
|
41
|
-
/** */
|
|
42
40
|
private totalOpsSizeBefore: number = 0;
|
|
43
41
|
|
|
42
|
+
public numNonRuntimeOps: number = 0;
|
|
44
43
|
/**
|
|
45
|
-
* Number of
|
|
46
|
-
* Is used to adjust
|
|
44
|
+
* Number of non-runtime ops at beginning of attempting to summarize.
|
|
45
|
+
* Is used to adjust numNonRuntimeOps appropriately after successful summarization.
|
|
47
46
|
*/
|
|
48
|
-
private
|
|
47
|
+
private numNonRuntimeOpsBefore: number = 0;
|
|
49
48
|
|
|
50
49
|
public numRuntimeOps: number = 0;
|
|
51
50
|
/**
|
|
52
|
-
* Number of
|
|
53
|
-
* Is used to adjust
|
|
51
|
+
* Number of runtime ops at beginning of attempting to summarize.
|
|
52
|
+
* Is used to adjust numRuntimeOps appropriately after successful summarization.
|
|
54
53
|
*/
|
|
55
|
-
private
|
|
54
|
+
private numRuntimeOpsBefore: number = 0;
|
|
56
55
|
|
|
57
56
|
constructor(
|
|
58
57
|
public lastOpSequenceNumber: number,
|
|
@@ -74,19 +73,19 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
|
|
|
74
73
|
summaryTime: Date.now(),
|
|
75
74
|
};
|
|
76
75
|
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
76
|
+
this.numNonRuntimeOpsBefore = this.numNonRuntimeOps;
|
|
77
|
+
this.numRuntimeOpsBefore = this.numRuntimeOps;
|
|
79
78
|
this.totalOpsSizeBefore = this.totalOpsSize;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
public markLastAttemptAsSuccessful() {
|
|
83
82
|
this._lastSuccessfulSummary = { ...this.lastAttempt };
|
|
84
83
|
|
|
85
|
-
this.numNonRuntimeOps -= this.
|
|
86
|
-
this.
|
|
84
|
+
this.numNonRuntimeOps -= this.numNonRuntimeOpsBefore;
|
|
85
|
+
this.numNonRuntimeOpsBefore = 0;
|
|
87
86
|
|
|
88
|
-
this.numRuntimeOps -= this.
|
|
89
|
-
this.
|
|
87
|
+
this.numRuntimeOps -= this.numRuntimeOpsBefore;
|
|
88
|
+
this.numRuntimeOpsBefore = 0;
|
|
90
89
|
|
|
91
90
|
this.totalOpsSize -= this.totalOpsSizeBefore;
|
|
92
91
|
this.totalOpsSizeBefore = 0;
|
|
@@ -489,6 +489,8 @@ type SummaryGeneratorOptionalTelemetryProperties =
|
|
|
489
489
|
| "opsSizesSinceLastSummary"
|
|
490
490
|
/** Delta between the number of non-runtime ops since the last summary */
|
|
491
491
|
| "nonRuntimeOpsSinceLastSummary"
|
|
492
|
+
/** Delta between the number of runtime ops since the last summary */
|
|
493
|
+
| "runtimeOpsSinceLastSummary"
|
|
492
494
|
/** Wether or not this instance contains adjusted metrics due to missing op data */
|
|
493
495
|
| "hasMissingOpData"
|
|
494
496
|
/** Time it took to generate the summary tree and stats. */
|
|
@@ -275,6 +275,9 @@ export class SummaryGenerator {
|
|
|
275
275
|
// Use record type to prevent unexpected value types
|
|
276
276
|
let summaryData: SubmitSummaryResult | undefined;
|
|
277
277
|
try {
|
|
278
|
+
// Need to save refSeqNum before we record new attempt (happens as part of submitSummaryCallback)
|
|
279
|
+
const lastAttemptRefSeqNum = this.heuristicData.lastAttempt.refSequenceNumber;
|
|
280
|
+
|
|
278
281
|
summaryData = await this.submitSummaryCallback({
|
|
279
282
|
fullTree,
|
|
280
283
|
refreshLatestAck,
|
|
@@ -282,16 +285,13 @@ export class SummaryGenerator {
|
|
|
282
285
|
cancellationToken,
|
|
283
286
|
});
|
|
284
287
|
|
|
285
|
-
this.heuristicData.recordAttempt(summaryData.referenceSequenceNumber);
|
|
286
|
-
|
|
287
288
|
// Cumulatively add telemetry properties based on how far generateSummary went.
|
|
288
289
|
const referenceSequenceNumber = summaryData.referenceSequenceNumber;
|
|
289
290
|
summarizeTelemetryProps = {
|
|
290
291
|
...summarizeTelemetryProps,
|
|
291
292
|
referenceSequenceNumber,
|
|
292
293
|
minimumSequenceNumber: summaryData.minimumSequenceNumber,
|
|
293
|
-
opsSinceLastAttempt:
|
|
294
|
-
referenceSequenceNumber - this.heuristicData.lastAttempt.refSequenceNumber,
|
|
294
|
+
opsSinceLastAttempt: referenceSequenceNumber - lastAttemptRefSeqNum,
|
|
295
295
|
opsSinceLastSummary:
|
|
296
296
|
referenceSequenceNumber -
|
|
297
297
|
this.heuristicData.lastSuccessfulSummary.refSequenceNumber,
|
|
@@ -481,6 +481,7 @@ export class SummaryGenerator {
|
|
|
481
481
|
hasMissingOpData: this.heuristicData.hasMissingOpData,
|
|
482
482
|
opsSizesSinceLastSummary: this.heuristicData.totalOpsSize,
|
|
483
483
|
nonRuntimeOpsSinceLastSummary: this.heuristicData.numNonRuntimeOps,
|
|
484
|
+
runtimeOpsSinceLastSummary: this.heuristicData.numRuntimeOps,
|
|
484
485
|
};
|
|
485
486
|
|
|
486
487
|
default:
|
|
@@ -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"]}
|