@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723
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/.eslintrc.js +1 -1
- package/dist/batchManager.d.ts +11 -6
- package/dist/batchManager.d.ts.map +1 -1
- package/dist/batchManager.js +23 -13
- package/dist/batchManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +74 -20
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +190 -137
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +6 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +14 -21
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +71 -57
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.js +1 -1
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStores.d.ts +11 -10
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +50 -20
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +36 -19
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +207 -121
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +3 -12
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/opCompressor.d.ts +18 -0
- package/dist/opCompressor.d.ts.map +1 -0
- package/dist/opCompressor.js +50 -0
- package/dist/opCompressor.js.map +1 -0
- package/dist/opDecompressor.d.ts +20 -0
- package/dist/opDecompressor.d.ts.map +1 -0
- package/dist/opDecompressor.js +72 -0
- package/dist/opDecompressor.js.map +1 -0
- 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 +6 -26
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +42 -62
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +3 -2
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +10 -3
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.js +7 -2
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.js +1 -1
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +0 -3
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +19 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts +4 -2
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +3 -2
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +10 -6
- package/dist/summaryManager.js.map +1 -1
- package/garbageCollection.md +27 -22
- package/lib/batchManager.d.ts +11 -6
- package/lib/batchManager.d.ts.map +1 -1
- package/lib/batchManager.js +23 -13
- package/lib/batchManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +74 -20
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +189 -136
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +6 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +14 -21
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +75 -61
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.js +1 -1
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStores.d.ts +11 -10
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +53 -23
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +36 -19
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +207 -121
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +3 -12
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -4
- package/lib/index.js.map +1 -1
- package/lib/opCompressor.d.ts +18 -0
- package/lib/opCompressor.d.ts.map +1 -0
- package/lib/opCompressor.js +46 -0
- package/lib/opCompressor.js.map +1 -0
- package/lib/opDecompressor.d.ts +20 -0
- package/lib/opDecompressor.d.ts.map +1 -0
- package/lib/opDecompressor.js +68 -0
- package/lib/opDecompressor.js.map +1 -0
- 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 +6 -26
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +42 -62
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +3 -2
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +10 -3
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.js +7 -2
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.js +1 -1
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +0 -3
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +19 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts +4 -2
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +3 -2
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +10 -6
- package/lib/summaryManager.js.map +1 -1
- package/package.json +37 -63
- package/prettier.config.cjs +8 -0
- package/src/batchManager.ts +32 -15
- package/src/containerRuntime.ts +260 -156
- package/src/dataStore.ts +13 -1
- package/src/dataStoreContext.ts +100 -76
- package/src/dataStoreContexts.ts +1 -1
- package/src/dataStores.ts +61 -23
- package/src/garbageCollection.ts +255 -126
- package/src/gcSweepReadyUsageDetection.ts +2 -10
- package/src/index.ts +4 -4
- package/src/opCompressor.ts +59 -0
- package/src/opDecompressor.ts +82 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +57 -96
- package/src/runningSummarizer.ts +11 -3
- package/src/scheduleManager.ts +1 -0
- package/src/summarizer.ts +6 -6
- package/src/summarizerClientElection.ts +1 -1
- package/src/summarizerHeuristics.ts +0 -3
- package/src/summarizerTypes.ts +20 -7
- package/src/summaryFormat.ts +4 -2
- package/src/summaryGenerator.ts +3 -2
- package/src/summaryManager.ts +18 -7
package/src/garbageCollection.ts
CHANGED
|
@@ -25,9 +25,11 @@ import {
|
|
|
25
25
|
ISummarizeResult,
|
|
26
26
|
ITelemetryContext,
|
|
27
27
|
IGarbageCollectionNodeData,
|
|
28
|
+
ISummaryTreeWithStats,
|
|
28
29
|
} from "@fluidframework/runtime-definitions";
|
|
29
30
|
import {
|
|
30
31
|
mergeStats,
|
|
32
|
+
packagePathToTelemetryProperty,
|
|
31
33
|
ReadAndParseBlob,
|
|
32
34
|
RefreshSummaryResult,
|
|
33
35
|
SummaryTreeBuilder,
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
ReadFluidDataStoreAttributes,
|
|
53
55
|
dataStoreAttributesBlobName,
|
|
54
56
|
IGCMetadata,
|
|
57
|
+
ICreateContainerMetadata,
|
|
55
58
|
} from "./summaryFormat";
|
|
56
59
|
|
|
57
60
|
/** This is the current version of garbage collection. */
|
|
@@ -61,6 +64,8 @@ const GCVersion = 1;
|
|
|
61
64
|
export const gcTreeKey = "gc";
|
|
62
65
|
// They prefix for GC blobs in the GC tree in summary.
|
|
63
66
|
export const gcBlobPrefix = "__gc";
|
|
67
|
+
// The key for tombstone blob in the GC tree in summary.
|
|
68
|
+
export const gcTombstoneBlobKey = "__tombstones";
|
|
64
69
|
|
|
65
70
|
// Feature gate key to turn GC on / off.
|
|
66
71
|
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
@@ -68,16 +73,16 @@ export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
|
68
73
|
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
69
74
|
// Feature gate key to turn GC test mode on / off.
|
|
70
75
|
export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
71
|
-
// Feature gate key to write GC data at the root of the summary tree.
|
|
72
|
-
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
73
76
|
// Feature gate key to expire a session after a set period of time.
|
|
74
77
|
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
75
|
-
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
|
|
76
|
-
export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
77
78
|
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
78
79
|
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
79
80
|
// Feature gate key to turn GC sweep log off.
|
|
80
81
|
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
82
|
+
// Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
|
|
83
|
+
export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
|
|
84
|
+
// Feature gate to enable throwing an error when tombstone object is used.
|
|
85
|
+
export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
|
|
81
86
|
|
|
82
87
|
// One day in milliseconds.
|
|
83
88
|
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
@@ -127,9 +132,9 @@ export interface IGarbageCollectionRuntime {
|
|
|
127
132
|
/** Returns the garbage collection data of the runtime. */
|
|
128
133
|
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
129
134
|
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
130
|
-
updateUsedRoutes(usedRoutes: string[]
|
|
131
|
-
/** After GC has run, called to
|
|
132
|
-
|
|
135
|
+
updateUsedRoutes(usedRoutes: string[]): void;
|
|
136
|
+
/** After GC has run, called to notify the runtime of routes that are unused in it. */
|
|
137
|
+
updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean): void;
|
|
133
138
|
/** Returns a referenced timestamp to be used to track unreferenced nodes. */
|
|
134
139
|
getCurrentReferenceTimestampMs(): number | undefined;
|
|
135
140
|
/** Returns the type of the GC node. */
|
|
@@ -144,9 +149,9 @@ export interface IGarbageCollector {
|
|
|
144
149
|
readonly shouldRunGC: boolean;
|
|
145
150
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
146
151
|
readonly summaryStateNeedsReset: boolean;
|
|
147
|
-
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
148
|
-
readonly writeDataAtRoot: boolean;
|
|
149
152
|
readonly trackGCState: boolean;
|
|
153
|
+
/** Initialize the state from the base snapshot after its creation. */
|
|
154
|
+
initializeBaseState(): Promise<void>;
|
|
150
155
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
151
156
|
collectGarbage(
|
|
152
157
|
options: { logger?: ITelemetryLogger; runSweep?: boolean; fullGC?: boolean; },
|
|
@@ -184,6 +189,7 @@ export interface IGarbageCollectorCreateParams {
|
|
|
184
189
|
readonly baseLogger: ITelemetryLogger;
|
|
185
190
|
readonly existing: boolean;
|
|
186
191
|
readonly metadata: IContainerRuntimeMetadata | undefined;
|
|
192
|
+
readonly createContainerMetadata: ICreateContainerMetadata;
|
|
187
193
|
readonly baseSnapshot: ISnapshotTree | undefined;
|
|
188
194
|
readonly isSummarizerClient: boolean;
|
|
189
195
|
readonly getNodePackagePath: (nodePath: string) => Promise<readonly string[] | undefined>;
|
|
@@ -191,7 +197,6 @@ export interface IGarbageCollectorCreateParams {
|
|
|
191
197
|
readonly readAndParseBlob: ReadAndParseBlob;
|
|
192
198
|
readonly activeConnection: () => boolean;
|
|
193
199
|
readonly getContainerDiagnosticId: () => string;
|
|
194
|
-
readonly snapshotCacheExpiryMs?: number;
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
/** The state of node that is unreferenced. */
|
|
@@ -221,6 +226,22 @@ interface IUnreferencedEventProps {
|
|
|
221
226
|
viaHandle?: boolean;
|
|
222
227
|
}
|
|
223
228
|
|
|
229
|
+
/**
|
|
230
|
+
* The GC data that is tracked for a summary that is submitted.
|
|
231
|
+
*/
|
|
232
|
+
interface IGCSummaryTrackingData {
|
|
233
|
+
serializedGCState: string | undefined;
|
|
234
|
+
serializedTombstones: string | undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The GC data that is read from a snapshot. It contains the GC state and tombstone state.
|
|
239
|
+
*/
|
|
240
|
+
interface IGCSnapshotData {
|
|
241
|
+
gcState: IGarbageCollectionState;
|
|
242
|
+
tombstones: string[] | undefined;
|
|
243
|
+
}
|
|
244
|
+
|
|
224
245
|
/**
|
|
225
246
|
* Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
|
|
226
247
|
* be deleted by the sweep phase.
|
|
@@ -377,16 +398,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
377
398
|
public readonly trackGCState: boolean;
|
|
378
399
|
|
|
379
400
|
private readonly testMode: boolean;
|
|
401
|
+
private readonly tombstoneMode: boolean;
|
|
380
402
|
private readonly mc: MonitoringContext;
|
|
381
403
|
|
|
382
|
-
/**
|
|
383
|
-
* Tells whether the GC data should be written to the root of the summary tree.
|
|
384
|
-
*/
|
|
385
|
-
private _writeDataAtRoot: boolean = true;
|
|
386
|
-
public get writeDataAtRoot(): boolean {
|
|
387
|
-
return this._writeDataAtRoot;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
404
|
/**
|
|
391
405
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
392
406
|
*
|
|
@@ -408,20 +422,24 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
408
422
|
|
|
409
423
|
// Keeps track of the GC state from the last run.
|
|
410
424
|
private previousGCDataFromLastRun: IGarbageCollectionData | undefined;
|
|
425
|
+
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
426
|
+
// outbound routes from that node.
|
|
427
|
+
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
428
|
+
private tombstones: string[] = [];
|
|
429
|
+
|
|
411
430
|
/**
|
|
412
|
-
* Keeps track of the
|
|
431
|
+
* Keeps track of the GC data from the latest summary successfully submitted to and acked from the server.
|
|
413
432
|
*/
|
|
414
|
-
private
|
|
433
|
+
private latestSummaryData: IGCSummaryTrackingData | undefined;
|
|
415
434
|
/**
|
|
416
|
-
* Keeps track of the
|
|
435
|
+
* Keeps track of the GC data from the last summary submitted to the server but not yet acked.
|
|
417
436
|
*/
|
|
418
|
-
private
|
|
419
|
-
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
420
|
-
// outbound routes from that node.
|
|
421
|
-
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
437
|
+
private pendingSummaryData: IGCSummaryTrackingData | undefined;
|
|
422
438
|
|
|
423
|
-
// Promise when resolved
|
|
424
|
-
private readonly
|
|
439
|
+
// Promise when resolved returns the GC data data in the base snapshot.
|
|
440
|
+
private readonly baseSnapshotDataP: Promise<IGCSnapshotData | undefined>;
|
|
441
|
+
// Promise when resolved initializes the GC state from the data in the base snapshot.
|
|
442
|
+
private readonly initializeGCStateFromBaseSnapshotP: Promise<void>;
|
|
425
443
|
// The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
|
|
426
444
|
private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
|
|
427
445
|
// Map of node ids to their unreferenced state tracker.
|
|
@@ -439,6 +457,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
439
457
|
private completedRuns = 0;
|
|
440
458
|
|
|
441
459
|
private readonly runtime: IGarbageCollectionRuntime;
|
|
460
|
+
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
442
461
|
private readonly gcOptions: IGCRuntimeOptions;
|
|
443
462
|
private readonly isSummarizerClient: boolean;
|
|
444
463
|
|
|
@@ -463,9 +482,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
463
482
|
sweepEnabled: this.sweepEnabled,
|
|
464
483
|
runGC: this.shouldRunGC,
|
|
465
484
|
runSweep: this.shouldRunSweep,
|
|
466
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
467
485
|
testMode: this.testMode,
|
|
486
|
+
tombstoneMode: this.tombstoneMode,
|
|
468
487
|
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
488
|
+
sweepTimeout: this.sweepTimeoutMs,
|
|
469
489
|
inactiveTimeout: this.inactiveTimeoutMs,
|
|
470
490
|
trackGCState: this.trackGCState,
|
|
471
491
|
...this.gcOptions,
|
|
@@ -479,6 +499,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
479
499
|
this.runtime = createParams.runtime;
|
|
480
500
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
481
501
|
this.gcOptions = createParams.gcOptions;
|
|
502
|
+
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
482
503
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
483
504
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
484
505
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -499,6 +520,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
499
520
|
|
|
500
521
|
let prevSummaryGCVersion: number | undefined;
|
|
501
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
525
|
+
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
|
|
526
|
+
*
|
|
527
|
+
* The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
|
|
528
|
+
* The buffer is added to account for any clock skew or other edge cases.
|
|
529
|
+
* We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
|
|
530
|
+
*/
|
|
531
|
+
function computeSweepTimeout(sessionExpiryTimeoutMs: number | undefined) {
|
|
532
|
+
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
533
|
+
const bufferMs = oneDayMs;
|
|
534
|
+
return sessionExpiryTimeoutMs &&
|
|
535
|
+
(sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
536
|
+
}
|
|
537
|
+
|
|
502
538
|
/**
|
|
503
539
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
504
540
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -513,6 +549,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
513
549
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
514
550
|
this.sweepEnabled = metadata?.sweepEnabled ?? false;
|
|
515
551
|
this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
|
|
552
|
+
this.sweepTimeoutMs =
|
|
553
|
+
metadata?.sweepTimeoutMs
|
|
554
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
|
|
516
555
|
} else {
|
|
517
556
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
518
557
|
// scenario but explicitly failing makes it clearer and promotes correct usage.
|
|
@@ -520,20 +559,28 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
520
559
|
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
521
560
|
}
|
|
522
561
|
|
|
562
|
+
// This Test Override only applies for new containers
|
|
563
|
+
const testOverrideSweepTimeoutMs =
|
|
564
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
565
|
+
|
|
523
566
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
524
567
|
// flag in GC options to false.
|
|
525
568
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
526
569
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
527
|
-
|
|
570
|
+
// ...unless we're using the TestOverride
|
|
571
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
528
572
|
|
|
529
573
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
530
574
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
531
575
|
this.sessionExpiryTimeoutMs = this.gcOptions.sessionExpiryTimeoutMs ?? defaultSessionExpiryDurationMs;
|
|
532
576
|
}
|
|
577
|
+
this.sweepTimeoutMs =
|
|
578
|
+
testOverrideSweepTimeoutMs
|
|
579
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
533
580
|
}
|
|
534
581
|
|
|
535
582
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
536
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
583
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
537
584
|
// If Test Override config is set, override Session Expiry timeout.
|
|
538
585
|
const overrideSessionExpiryTimeoutMs =
|
|
539
586
|
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
@@ -544,21 +591,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
544
591
|
() => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); },
|
|
545
592
|
);
|
|
546
593
|
this.sessionExpiryTimer.start();
|
|
547
|
-
|
|
548
|
-
// TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
|
|
549
|
-
// This unblocks the Sweep Log (see logSweepEvents function).
|
|
550
|
-
// This will be removed before sweep is fully implemented.
|
|
551
|
-
const snapshotCacheExpiryMs = createParams.snapshotCacheExpiryMs ?? 5 * 24 * 60 * 60 * 1000;
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
555
|
-
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
|
|
556
|
-
* added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
|
|
557
|
-
* but make it one day to be safe.
|
|
558
|
-
*/
|
|
559
|
-
if (snapshotCacheExpiryMs !== undefined) {
|
|
560
|
-
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + oneDayMs;
|
|
561
|
-
}
|
|
562
594
|
}
|
|
563
595
|
|
|
564
596
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -585,16 +617,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
585
617
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
586
618
|
*
|
|
587
619
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
588
|
-
*
|
|
589
620
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
590
|
-
*
|
|
591
|
-
*
|
|
621
|
+
* 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
|
|
622
|
+
* the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
|
|
623
|
+
* 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
592
624
|
* feature flag.
|
|
593
625
|
*/
|
|
594
|
-
this.shouldRunSweep =
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
626
|
+
this.shouldRunSweep =
|
|
627
|
+
this.shouldRunGC
|
|
628
|
+
&& this.sweepTimeoutMs !== undefined
|
|
629
|
+
&& (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
|
|
598
630
|
|
|
599
631
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
600
632
|
|
|
@@ -610,20 +642,17 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
610
642
|
|
|
611
643
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
612
644
|
this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? this.gcOptions.runGCInTestMode === true;
|
|
645
|
+
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
646
|
+
this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
613
647
|
|
|
614
|
-
// GC state
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
619
|
-
// contain GC tree and GC is enabled.
|
|
620
|
-
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
621
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
622
|
-
}
|
|
648
|
+
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
649
|
+
// contain GC tree and GC is enabled.
|
|
650
|
+
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
651
|
+
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
623
652
|
|
|
624
|
-
// Get the GC
|
|
625
|
-
//
|
|
626
|
-
|
|
653
|
+
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
654
|
+
// it involves fetching blobs from storage which is expensive.
|
|
655
|
+
this.baseSnapshotDataP = new LazyPromise<IGCSnapshotData | undefined>(async () => {
|
|
627
656
|
if (baseSnapshot === undefined) {
|
|
628
657
|
return undefined;
|
|
629
658
|
}
|
|
@@ -632,16 +661,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
632
661
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
633
662
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
634
663
|
if (gcSnapshotTree !== undefined) {
|
|
635
|
-
|
|
636
|
-
this._writeDataAtRoot = true;
|
|
637
|
-
const baseGCState = await getGCStateFromSnapshot(
|
|
664
|
+
return getGCDataFromSnapshot(
|
|
638
665
|
gcSnapshotTree,
|
|
639
666
|
readAndParseBlob,
|
|
640
667
|
);
|
|
641
|
-
if (this.trackGCState) {
|
|
642
|
-
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
643
|
-
}
|
|
644
|
-
return baseGCState;
|
|
645
668
|
}
|
|
646
669
|
|
|
647
670
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
@@ -685,7 +708,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
685
708
|
}
|
|
686
709
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
687
710
|
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
688
|
-
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
711
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
|
|
689
712
|
} catch (error) {
|
|
690
713
|
const dpe = DataProcessingError.wrapIfUnrecognized(
|
|
691
714
|
error,
|
|
@@ -697,11 +720,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
697
720
|
});
|
|
698
721
|
|
|
699
722
|
/**
|
|
700
|
-
* Set up the initializer which initializes the
|
|
701
|
-
*
|
|
702
|
-
*
|
|
723
|
+
* Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
|
|
724
|
+
* connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
|
|
725
|
+
* GC state and updates their inactive or sweep ready state.
|
|
703
726
|
*/
|
|
704
|
-
this.
|
|
727
|
+
this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
|
|
705
728
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
706
729
|
/**
|
|
707
730
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
@@ -720,19 +743,19 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
720
743
|
return;
|
|
721
744
|
}
|
|
722
745
|
|
|
723
|
-
const
|
|
746
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
724
747
|
/**
|
|
725
|
-
* The base
|
|
748
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
726
749
|
* 1. The first summary created by the detached container.
|
|
727
750
|
* 2. A summary that was generated with GC disabled.
|
|
728
751
|
* 3. A summary that was generated before GC even existed.
|
|
729
752
|
*/
|
|
730
|
-
if (
|
|
753
|
+
if (baseSnapshotData === undefined) {
|
|
731
754
|
return;
|
|
732
755
|
}
|
|
733
756
|
|
|
734
757
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
735
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
758
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
736
759
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
737
760
|
this.unreferencedNodesState.set(
|
|
738
761
|
nodeId,
|
|
@@ -747,18 +770,26 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
747
770
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
748
771
|
}
|
|
749
772
|
this.previousGCDataFromLastRun = { gcNodes };
|
|
773
|
+
|
|
774
|
+
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
775
|
+
if (this.trackGCState) {
|
|
776
|
+
this.latestSummaryData = {
|
|
777
|
+
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
778
|
+
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
750
781
|
});
|
|
751
782
|
|
|
752
783
|
// Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
|
|
753
784
|
// which the caller uses to initialize each node's GC state.
|
|
754
785
|
this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
|
|
755
|
-
const
|
|
756
|
-
if (
|
|
786
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
787
|
+
if (baseSnapshotData === undefined) {
|
|
757
788
|
return new Map();
|
|
758
789
|
}
|
|
759
790
|
|
|
760
791
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
761
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
792
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
762
793
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
763
794
|
}
|
|
764
795
|
// Run GC on the nodes in the base summary to get the routes used in each node in the container.
|
|
@@ -769,7 +800,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
769
800
|
const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
|
|
770
801
|
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
771
802
|
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
772
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
803
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
773
804
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
774
805
|
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
775
806
|
if (dataStoreGCDetails !== undefined) {
|
|
@@ -790,6 +821,27 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
790
821
|
}
|
|
791
822
|
}
|
|
792
823
|
|
|
824
|
+
/**
|
|
825
|
+
* Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
|
|
826
|
+
* before they are loaded or used. This is important to get accurate information of whether tombstoned object are
|
|
827
|
+
* in use or not.
|
|
828
|
+
*/
|
|
829
|
+
public async initializeBaseState(): Promise<void> {
|
|
830
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
831
|
+
/**
|
|
832
|
+
* The base snapshot data or tombstone state will not be present if the container is loaded from:
|
|
833
|
+
* 1. The first summary created by the detached container.
|
|
834
|
+
* 2. A summary that was generated with GC disabled.
|
|
835
|
+
* 3. A summary that was generated before GC even existed.
|
|
836
|
+
* 4. A summary that was generated with tombstone feature disabled.
|
|
837
|
+
*/
|
|
838
|
+
if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
this.tombstones = baseSnapshotData.tombstones;
|
|
842
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
843
|
+
}
|
|
844
|
+
|
|
793
845
|
/**
|
|
794
846
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
795
847
|
* to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
|
|
@@ -798,16 +850,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
798
850
|
*/
|
|
799
851
|
public setConnectionState(connected: boolean, clientId?: string | undefined): void {
|
|
800
852
|
/**
|
|
801
|
-
* For
|
|
853
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
802
854
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
803
855
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
804
856
|
* could affect the GC state will have been processed.
|
|
805
857
|
*
|
|
858
|
+
* If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
|
|
859
|
+
* InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
|
|
860
|
+
* the receiving summarizer client.
|
|
861
|
+
*
|
|
806
862
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
807
863
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
808
864
|
*/
|
|
809
|
-
if (this.activeConnection() &&
|
|
810
|
-
this.
|
|
865
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
866
|
+
this.initializeGCStateFromBaseSnapshotP.catch((error) => {});
|
|
811
867
|
}
|
|
812
868
|
}
|
|
813
869
|
|
|
@@ -856,15 +912,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
856
912
|
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
857
913
|
|
|
858
914
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
859
|
-
event.end({ ...gcStats });
|
|
915
|
+
event.end({ ...gcStats, timestamp: currentReferenceTimestampMs });
|
|
860
916
|
this.completedRuns++;
|
|
861
917
|
return gcStats;
|
|
862
918
|
}, { end: true, cancel: "error" });
|
|
863
919
|
}
|
|
864
920
|
|
|
865
921
|
private async runPreGCSteps() {
|
|
866
|
-
// Ensure that
|
|
867
|
-
await this.
|
|
922
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
923
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
868
924
|
// Let the runtime update its pending state before GC runs.
|
|
869
925
|
await this.runtime.updateStateBeforeGC();
|
|
870
926
|
}
|
|
@@ -885,7 +941,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
885
941
|
|
|
886
942
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
887
943
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
888
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
944
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
889
945
|
|
|
890
946
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
891
947
|
// delete these objects here instead.
|
|
@@ -894,7 +950,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
894
950
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
895
951
|
// involving access to deleted data.
|
|
896
952
|
if (this.testMode) {
|
|
897
|
-
this.runtime.
|
|
953
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
|
|
954
|
+
} else if (this.tombstoneMode) {
|
|
955
|
+
// If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
|
|
956
|
+
// scenarios involving access to "deleted" data without actually deleting the data from summaries.
|
|
957
|
+
// Note: we will not tombstone in test mode
|
|
958
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
898
959
|
}
|
|
899
960
|
|
|
900
961
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
@@ -927,35 +988,76 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
927
988
|
};
|
|
928
989
|
}
|
|
929
990
|
|
|
930
|
-
const
|
|
991
|
+
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
992
|
+
const serializedTombstones = this.tombstoneMode
|
|
993
|
+
? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
|
|
994
|
+
: undefined;
|
|
931
995
|
|
|
932
996
|
/**
|
|
933
|
-
*
|
|
934
|
-
*
|
|
997
|
+
* Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
|
|
998
|
+
* summary, send summary handles for them. Otherwise, send the data in summary blobs.
|
|
935
999
|
*/
|
|
936
1000
|
if (this.trackGCState) {
|
|
937
|
-
this.
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
this.
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
1001
|
+
this.pendingSummaryData = { serializedGCState, serializedTombstones };
|
|
1002
|
+
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
1003
|
+
// If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
|
|
1004
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
1005
|
+
&& this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
1006
|
+
const stats = mergeStats();
|
|
1007
|
+
stats.handleNodeCount++;
|
|
1008
|
+
return {
|
|
1009
|
+
summary: {
|
|
1010
|
+
type: SummaryType.Handle,
|
|
1011
|
+
handle: `/${gcTreeKey}`,
|
|
1012
|
+
handleType: SummaryType.Tree,
|
|
1013
|
+
},
|
|
1014
|
+
stats,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// If either or both of GC state or tombstone state changed, build a GC summary tree.
|
|
1019
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
954
1020
|
}
|
|
955
1021
|
}
|
|
1022
|
+
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
1023
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
1024
|
+
}
|
|
956
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* Builds the GC summary tree which contains GC state and tombstone state.
|
|
1028
|
+
* If trackState is false, both GC state and tombstone state are written as summary blobs.
|
|
1029
|
+
* If trackState is true, summary blob is written for GC state or tombstone state if they changed.
|
|
1030
|
+
* @param serializedGCState - The GC state serialized as string.
|
|
1031
|
+
* @param serializedTombstones - THe tombstone state serialized as string.
|
|
1032
|
+
* @param trackState - Whether we are tracking GC state across summaries.
|
|
1033
|
+
* @returns the GC summary tree.
|
|
1034
|
+
*/
|
|
1035
|
+
private buildGCSummaryTree(
|
|
1036
|
+
serializedGCState: string,
|
|
1037
|
+
serializedTombstones: string | undefined,
|
|
1038
|
+
trackState: boolean,
|
|
1039
|
+
): ISummaryTreeWithStats {
|
|
1040
|
+
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
957
1041
|
const builder = new SummaryTreeBuilder();
|
|
958
|
-
|
|
1042
|
+
|
|
1043
|
+
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
1044
|
+
if (this.latestSummaryData?.serializedGCState === serializedGCState && trackState) {
|
|
1045
|
+
builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
|
|
1046
|
+
} else {
|
|
1047
|
+
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// If there is no tombstone data, return only the GC state.
|
|
1051
|
+
if (serializedTombstones === undefined) {
|
|
1052
|
+
return builder.getSummaryTree();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
1056
|
+
if (this.latestSummaryData?.serializedTombstones === serializedTombstones && trackState) {
|
|
1057
|
+
builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
|
|
1058
|
+
} else {
|
|
1059
|
+
builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
|
|
1060
|
+
}
|
|
959
1061
|
return builder.getSummaryTree();
|
|
960
1062
|
}
|
|
961
1063
|
|
|
@@ -968,6 +1070,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
968
1070
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
969
1071
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
970
1072
|
sweepEnabled: this.sweepEnabled,
|
|
1073
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
971
1074
|
};
|
|
972
1075
|
}
|
|
973
1076
|
|
|
@@ -997,8 +1100,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
997
1100
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
998
1101
|
this.initialStateNeedsReset = false;
|
|
999
1102
|
if (this.trackGCState) {
|
|
1000
|
-
this.
|
|
1001
|
-
this.
|
|
1103
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
1104
|
+
this.pendingSummaryData = undefined;
|
|
1002
1105
|
}
|
|
1003
1106
|
return;
|
|
1004
1107
|
}
|
|
@@ -1013,15 +1116,18 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1013
1116
|
|
|
1014
1117
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
1015
1118
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
1016
|
-
const
|
|
1119
|
+
const latestGCData = await getGCDataFromSnapshot(
|
|
1017
1120
|
gcSnapshotTree,
|
|
1018
1121
|
readAndParseBlob,
|
|
1019
1122
|
);
|
|
1020
|
-
this.
|
|
1123
|
+
this.latestSummaryData = {
|
|
1124
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
1125
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
1126
|
+
};
|
|
1021
1127
|
} else {
|
|
1022
|
-
this.
|
|
1128
|
+
this.latestSummaryData = undefined;
|
|
1023
1129
|
}
|
|
1024
|
-
this.
|
|
1130
|
+
this.pendingSummaryData = undefined;
|
|
1025
1131
|
}
|
|
1026
1132
|
|
|
1027
1133
|
/**
|
|
@@ -1099,6 +1205,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1099
1205
|
currentReferenceTimestampMs: number,
|
|
1100
1206
|
) {
|
|
1101
1207
|
this.previousGCDataFromLastRun = cloneGCData(gcData);
|
|
1208
|
+
this.tombstones = [];
|
|
1102
1209
|
this.newReferencesSinceLastRun.clear();
|
|
1103
1210
|
|
|
1104
1211
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -1131,6 +1238,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1131
1238
|
);
|
|
1132
1239
|
} else {
|
|
1133
1240
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
1241
|
+
if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
|
|
1242
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
1243
|
+
if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
|
|
1244
|
+
this.tombstones.push(nodeId);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1134
1247
|
}
|
|
1135
1248
|
}
|
|
1136
1249
|
}
|
|
@@ -1156,7 +1269,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1156
1269
|
this.newReferencesSinceLastRun,
|
|
1157
1270
|
);
|
|
1158
1271
|
|
|
1159
|
-
if (
|
|
1272
|
+
if (missingExplicitReferences.length > 0) {
|
|
1160
1273
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
1161
1274
|
const event: ITelemetryPerformanceEvent = {
|
|
1162
1275
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -1413,6 +1526,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1413
1526
|
: this.sweepTimeoutMs,
|
|
1414
1527
|
completedGCRuns: this.completedRuns,
|
|
1415
1528
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
1529
|
+
...this.createContainerMetadata,
|
|
1416
1530
|
externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
|
|
1417
1531
|
viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
|
|
1418
1532
|
fromId: fromNodeId,
|
|
@@ -1421,16 +1535,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1421
1535
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
1422
1536
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
1423
1537
|
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
1538
|
+
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
1539
|
+
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
1424
1540
|
if (this.isSummarizerClient) {
|
|
1425
1541
|
this.pendingEventsQueue.push({ ...propsToLog, usageType, state });
|
|
1426
1542
|
} else {
|
|
1427
1543
|
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
1428
1544
|
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
1545
|
+
// Events generated:
|
|
1546
|
+
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1429
1547
|
if (usageType === "Loaded") {
|
|
1430
1548
|
this.mc.logger.sendErrorEvent({
|
|
1431
1549
|
...propsToLog,
|
|
1432
1550
|
eventName: `${state}Object_${usageType}`,
|
|
1433
|
-
pkg: packagePath
|
|
1551
|
+
pkg: packagePathToTelemetryProperty(packagePath),
|
|
1434
1552
|
stack: generateStack(),
|
|
1435
1553
|
});
|
|
1436
1554
|
}
|
|
@@ -1445,6 +1563,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1445
1563
|
}
|
|
1446
1564
|
|
|
1447
1565
|
private async logUnreferencedEvents(logger: ITelemetryLogger) {
|
|
1566
|
+
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
1567
|
+
// summary time they are then logged.
|
|
1568
|
+
// Events generated:
|
|
1569
|
+
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
1570
|
+
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
1448
1571
|
for (const eventProps of this.pendingEventsQueue) {
|
|
1449
1572
|
const { usageType, state, ...propsToLog } = eventProps;
|
|
1450
1573
|
/**
|
|
@@ -1471,15 +1594,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1471
1594
|
}
|
|
1472
1595
|
|
|
1473
1596
|
/**
|
|
1474
|
-
* Gets the garbage collection state from the given snapshot tree.
|
|
1475
|
-
* Merge the GC state from all such blobs
|
|
1597
|
+
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1598
|
+
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1476
1599
|
*/
|
|
1477
|
-
async function
|
|
1600
|
+
async function getGCDataFromSnapshot(
|
|
1478
1601
|
gcSnapshotTree: ISnapshotTree,
|
|
1479
1602
|
readAndParseBlob: ReadAndParseBlob,
|
|
1480
|
-
): Promise<
|
|
1603
|
+
): Promise<IGCSnapshotData> {
|
|
1481
1604
|
let rootGCState: IGarbageCollectionState = { gcNodes: {} };
|
|
1605
|
+
let tombstones: string[] | undefined;
|
|
1482
1606
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1607
|
+
if (key === gcTombstoneBlobKey) {
|
|
1608
|
+
tombstones = await readAndParseBlob<string[]>(gcSnapshotTree.blobs[key]);
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1483
1612
|
// Skip blobs that do not start with the GC prefix.
|
|
1484
1613
|
if (!key.startsWith(gcBlobPrefix)) {
|
|
1485
1614
|
continue;
|
|
@@ -1494,7 +1623,7 @@ async function getGCStateFromSnapshot(
|
|
|
1494
1623
|
// Merge the GC state of this blob into the root GC state.
|
|
1495
1624
|
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1496
1625
|
}
|
|
1497
|
-
return rootGCState;
|
|
1626
|
+
return { gcState: rootGCState, tombstones };
|
|
1498
1627
|
}
|
|
1499
1628
|
|
|
1500
1629
|
function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
|