@fluidframework/container-runtime 2.0.0-dev.1.4.5.105745 → 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 +208 -122
- 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 +257 -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,15 +25,18 @@ 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,
|
|
34
36
|
} from "@fluidframework/runtime-utils";
|
|
35
37
|
import {
|
|
36
38
|
ChildLogger,
|
|
39
|
+
generateStack,
|
|
37
40
|
loggerToMonitoringContext,
|
|
38
41
|
MonitoringContext,
|
|
39
42
|
PerformanceEvent,
|
|
@@ -51,6 +54,7 @@ import {
|
|
|
51
54
|
ReadFluidDataStoreAttributes,
|
|
52
55
|
dataStoreAttributesBlobName,
|
|
53
56
|
IGCMetadata,
|
|
57
|
+
ICreateContainerMetadata,
|
|
54
58
|
} from "./summaryFormat";
|
|
55
59
|
|
|
56
60
|
/** This is the current version of garbage collection. */
|
|
@@ -60,6 +64,8 @@ const GCVersion = 1;
|
|
|
60
64
|
export const gcTreeKey = "gc";
|
|
61
65
|
// They prefix for GC blobs in the GC tree in summary.
|
|
62
66
|
export const gcBlobPrefix = "__gc";
|
|
67
|
+
// The key for tombstone blob in the GC tree in summary.
|
|
68
|
+
export const gcTombstoneBlobKey = "__tombstones";
|
|
63
69
|
|
|
64
70
|
// Feature gate key to turn GC on / off.
|
|
65
71
|
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
@@ -67,16 +73,16 @@ export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
|
67
73
|
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
68
74
|
// Feature gate key to turn GC test mode on / off.
|
|
69
75
|
export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
70
|
-
// Feature gate key to write GC data at the root of the summary tree.
|
|
71
|
-
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
72
76
|
// Feature gate key to expire a session after a set period of time.
|
|
73
77
|
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
74
|
-
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
|
|
75
|
-
export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
76
78
|
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
77
79
|
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
78
80
|
// Feature gate key to turn GC sweep log off.
|
|
79
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";
|
|
80
86
|
|
|
81
87
|
// One day in milliseconds.
|
|
82
88
|
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
@@ -126,9 +132,9 @@ export interface IGarbageCollectionRuntime {
|
|
|
126
132
|
/** Returns the garbage collection data of the runtime. */
|
|
127
133
|
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
128
134
|
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
129
|
-
updateUsedRoutes(usedRoutes: string[]
|
|
130
|
-
/** After GC has run, called to
|
|
131
|
-
|
|
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;
|
|
132
138
|
/** Returns a referenced timestamp to be used to track unreferenced nodes. */
|
|
133
139
|
getCurrentReferenceTimestampMs(): number | undefined;
|
|
134
140
|
/** Returns the type of the GC node. */
|
|
@@ -143,9 +149,9 @@ export interface IGarbageCollector {
|
|
|
143
149
|
readonly shouldRunGC: boolean;
|
|
144
150
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
145
151
|
readonly summaryStateNeedsReset: boolean;
|
|
146
|
-
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
147
|
-
readonly writeDataAtRoot: boolean;
|
|
148
152
|
readonly trackGCState: boolean;
|
|
153
|
+
/** Initialize the state from the base snapshot after its creation. */
|
|
154
|
+
initializeBaseState(): Promise<void>;
|
|
149
155
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
150
156
|
collectGarbage(
|
|
151
157
|
options: { logger?: ITelemetryLogger; runSweep?: boolean; fullGC?: boolean; },
|
|
@@ -183,6 +189,7 @@ export interface IGarbageCollectorCreateParams {
|
|
|
183
189
|
readonly baseLogger: ITelemetryLogger;
|
|
184
190
|
readonly existing: boolean;
|
|
185
191
|
readonly metadata: IContainerRuntimeMetadata | undefined;
|
|
192
|
+
readonly createContainerMetadata: ICreateContainerMetadata;
|
|
186
193
|
readonly baseSnapshot: ISnapshotTree | undefined;
|
|
187
194
|
readonly isSummarizerClient: boolean;
|
|
188
195
|
readonly getNodePackagePath: (nodePath: string) => Promise<readonly string[] | undefined>;
|
|
@@ -190,7 +197,6 @@ export interface IGarbageCollectorCreateParams {
|
|
|
190
197
|
readonly readAndParseBlob: ReadAndParseBlob;
|
|
191
198
|
readonly activeConnection: () => boolean;
|
|
192
199
|
readonly getContainerDiagnosticId: () => string;
|
|
193
|
-
readonly snapshotCacheExpiryMs?: number;
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
/** The state of node that is unreferenced. */
|
|
@@ -220,6 +226,22 @@ interface IUnreferencedEventProps {
|
|
|
220
226
|
viaHandle?: boolean;
|
|
221
227
|
}
|
|
222
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
|
+
|
|
223
245
|
/**
|
|
224
246
|
* Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
|
|
225
247
|
* be deleted by the sweep phase.
|
|
@@ -376,16 +398,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
376
398
|
public readonly trackGCState: boolean;
|
|
377
399
|
|
|
378
400
|
private readonly testMode: boolean;
|
|
401
|
+
private readonly tombstoneMode: boolean;
|
|
379
402
|
private readonly mc: MonitoringContext;
|
|
380
403
|
|
|
381
|
-
/**
|
|
382
|
-
* Tells whether the GC data should be written to the root of the summary tree.
|
|
383
|
-
*/
|
|
384
|
-
private _writeDataAtRoot: boolean = true;
|
|
385
|
-
public get writeDataAtRoot(): boolean {
|
|
386
|
-
return this._writeDataAtRoot;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
404
|
/**
|
|
390
405
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
391
406
|
*
|
|
@@ -407,20 +422,24 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
407
422
|
|
|
408
423
|
// Keeps track of the GC state from the last run.
|
|
409
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
|
+
|
|
410
430
|
/**
|
|
411
|
-
* Keeps track of the
|
|
431
|
+
* Keeps track of the GC data from the latest summary successfully submitted to and acked from the server.
|
|
412
432
|
*/
|
|
413
|
-
private
|
|
433
|
+
private latestSummaryData: IGCSummaryTrackingData | undefined;
|
|
414
434
|
/**
|
|
415
|
-
* Keeps track of the
|
|
435
|
+
* Keeps track of the GC data from the last summary submitted to the server but not yet acked.
|
|
416
436
|
*/
|
|
417
|
-
private
|
|
418
|
-
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
419
|
-
// outbound routes from that node.
|
|
420
|
-
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
437
|
+
private pendingSummaryData: IGCSummaryTrackingData | undefined;
|
|
421
438
|
|
|
422
|
-
// Promise when resolved
|
|
423
|
-
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>;
|
|
424
443
|
// The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
|
|
425
444
|
private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
|
|
426
445
|
// Map of node ids to their unreferenced state tracker.
|
|
@@ -438,6 +457,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
438
457
|
private completedRuns = 0;
|
|
439
458
|
|
|
440
459
|
private readonly runtime: IGarbageCollectionRuntime;
|
|
460
|
+
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
441
461
|
private readonly gcOptions: IGCRuntimeOptions;
|
|
442
462
|
private readonly isSummarizerClient: boolean;
|
|
443
463
|
|
|
@@ -462,9 +482,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
462
482
|
sweepEnabled: this.sweepEnabled,
|
|
463
483
|
runGC: this.shouldRunGC,
|
|
464
484
|
runSweep: this.shouldRunSweep,
|
|
465
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
466
485
|
testMode: this.testMode,
|
|
486
|
+
tombstoneMode: this.tombstoneMode,
|
|
467
487
|
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
488
|
+
sweepTimeout: this.sweepTimeoutMs,
|
|
468
489
|
inactiveTimeout: this.inactiveTimeoutMs,
|
|
469
490
|
trackGCState: this.trackGCState,
|
|
470
491
|
...this.gcOptions,
|
|
@@ -478,6 +499,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
478
499
|
this.runtime = createParams.runtime;
|
|
479
500
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
480
501
|
this.gcOptions = createParams.gcOptions;
|
|
502
|
+
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
481
503
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
482
504
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
483
505
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -498,6 +520,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
498
520
|
|
|
499
521
|
let prevSummaryGCVersion: number | undefined;
|
|
500
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
|
+
|
|
501
538
|
/**
|
|
502
539
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
503
540
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -512,6 +549,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
512
549
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
513
550
|
this.sweepEnabled = metadata?.sweepEnabled ?? false;
|
|
514
551
|
this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
|
|
552
|
+
this.sweepTimeoutMs =
|
|
553
|
+
metadata?.sweepTimeoutMs
|
|
554
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
|
|
515
555
|
} else {
|
|
516
556
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
517
557
|
// scenario but explicitly failing makes it clearer and promotes correct usage.
|
|
@@ -519,20 +559,28 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
519
559
|
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
520
560
|
}
|
|
521
561
|
|
|
562
|
+
// This Test Override only applies for new containers
|
|
563
|
+
const testOverrideSweepTimeoutMs =
|
|
564
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
565
|
+
|
|
522
566
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
523
567
|
// flag in GC options to false.
|
|
524
568
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
525
569
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
526
|
-
|
|
570
|
+
// ...unless we're using the TestOverride
|
|
571
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
527
572
|
|
|
528
573
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
529
574
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
530
575
|
this.sessionExpiryTimeoutMs = this.gcOptions.sessionExpiryTimeoutMs ?? defaultSessionExpiryDurationMs;
|
|
531
576
|
}
|
|
577
|
+
this.sweepTimeoutMs =
|
|
578
|
+
testOverrideSweepTimeoutMs
|
|
579
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
532
580
|
}
|
|
533
581
|
|
|
534
582
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
535
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
583
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
536
584
|
// If Test Override config is set, override Session Expiry timeout.
|
|
537
585
|
const overrideSessionExpiryTimeoutMs =
|
|
538
586
|
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
@@ -543,21 +591,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
543
591
|
() => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); },
|
|
544
592
|
);
|
|
545
593
|
this.sessionExpiryTimer.start();
|
|
546
|
-
|
|
547
|
-
// TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
|
|
548
|
-
// This unblocks the Sweep Log (see logSweepEvents function).
|
|
549
|
-
// This will be removed before sweep is fully implemented.
|
|
550
|
-
const snapshotCacheExpiryMs = createParams.snapshotCacheExpiryMs ?? 5 * 24 * 60 * 60 * 1000;
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
554
|
-
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
|
|
555
|
-
* added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
|
|
556
|
-
* but make it one day to be safe.
|
|
557
|
-
*/
|
|
558
|
-
if (snapshotCacheExpiryMs !== undefined) {
|
|
559
|
-
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + oneDayMs;
|
|
560
|
-
}
|
|
561
594
|
}
|
|
562
595
|
|
|
563
596
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -584,16 +617,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
584
617
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
585
618
|
*
|
|
586
619
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
587
|
-
*
|
|
588
620
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
589
|
-
*
|
|
590
|
-
*
|
|
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
|
|
591
624
|
* feature flag.
|
|
592
625
|
*/
|
|
593
|
-
this.shouldRunSweep =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
626
|
+
this.shouldRunSweep =
|
|
627
|
+
this.shouldRunGC
|
|
628
|
+
&& this.sweepTimeoutMs !== undefined
|
|
629
|
+
&& (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
|
|
597
630
|
|
|
598
631
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
599
632
|
|
|
@@ -609,20 +642,17 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
609
642
|
|
|
610
643
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
611
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;
|
|
612
647
|
|
|
613
|
-
// GC state
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
618
|
-
// contain GC tree and GC is enabled.
|
|
619
|
-
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
620
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
621
|
-
}
|
|
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;
|
|
622
652
|
|
|
623
|
-
// Get the GC
|
|
624
|
-
//
|
|
625
|
-
|
|
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 () => {
|
|
626
656
|
if (baseSnapshot === undefined) {
|
|
627
657
|
return undefined;
|
|
628
658
|
}
|
|
@@ -631,16 +661,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
631
661
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
632
662
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
633
663
|
if (gcSnapshotTree !== undefined) {
|
|
634
|
-
|
|
635
|
-
this._writeDataAtRoot = true;
|
|
636
|
-
const baseGCState = await getGCStateFromSnapshot(
|
|
664
|
+
return getGCDataFromSnapshot(
|
|
637
665
|
gcSnapshotTree,
|
|
638
666
|
readAndParseBlob,
|
|
639
667
|
);
|
|
640
|
-
if (this.trackGCState) {
|
|
641
|
-
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
642
|
-
}
|
|
643
|
-
return baseGCState;
|
|
644
668
|
}
|
|
645
669
|
|
|
646
670
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
@@ -684,7 +708,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
684
708
|
}
|
|
685
709
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
686
710
|
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
687
|
-
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
711
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
|
|
688
712
|
} catch (error) {
|
|
689
713
|
const dpe = DataProcessingError.wrapIfUnrecognized(
|
|
690
714
|
error,
|
|
@@ -696,11 +720,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
696
720
|
});
|
|
697
721
|
|
|
698
722
|
/**
|
|
699
|
-
* Set up the initializer which initializes the
|
|
700
|
-
*
|
|
701
|
-
*
|
|
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.
|
|
702
726
|
*/
|
|
703
|
-
this.
|
|
727
|
+
this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
|
|
704
728
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
705
729
|
/**
|
|
706
730
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
@@ -719,19 +743,19 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
719
743
|
return;
|
|
720
744
|
}
|
|
721
745
|
|
|
722
|
-
const
|
|
746
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
723
747
|
/**
|
|
724
|
-
* The base
|
|
748
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
725
749
|
* 1. The first summary created by the detached container.
|
|
726
750
|
* 2. A summary that was generated with GC disabled.
|
|
727
751
|
* 3. A summary that was generated before GC even existed.
|
|
728
752
|
*/
|
|
729
|
-
if (
|
|
753
|
+
if (baseSnapshotData === undefined) {
|
|
730
754
|
return;
|
|
731
755
|
}
|
|
732
756
|
|
|
733
757
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
734
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
758
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
735
759
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
736
760
|
this.unreferencedNodesState.set(
|
|
737
761
|
nodeId,
|
|
@@ -746,18 +770,26 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
746
770
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
747
771
|
}
|
|
748
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
|
+
}
|
|
749
781
|
});
|
|
750
782
|
|
|
751
783
|
// Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
|
|
752
784
|
// which the caller uses to initialize each node's GC state.
|
|
753
785
|
this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
|
|
754
|
-
const
|
|
755
|
-
if (
|
|
786
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
787
|
+
if (baseSnapshotData === undefined) {
|
|
756
788
|
return new Map();
|
|
757
789
|
}
|
|
758
790
|
|
|
759
791
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
760
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
792
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
761
793
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
762
794
|
}
|
|
763
795
|
// Run GC on the nodes in the base summary to get the routes used in each node in the container.
|
|
@@ -768,7 +800,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
768
800
|
const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
|
|
769
801
|
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
770
802
|
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
771
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
803
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
772
804
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
773
805
|
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
774
806
|
if (dataStoreGCDetails !== undefined) {
|
|
@@ -789,6 +821,27 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
789
821
|
}
|
|
790
822
|
}
|
|
791
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
|
+
|
|
792
845
|
/**
|
|
793
846
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
794
847
|
* to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
|
|
@@ -797,16 +850,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
797
850
|
*/
|
|
798
851
|
public setConnectionState(connected: boolean, clientId?: string | undefined): void {
|
|
799
852
|
/**
|
|
800
|
-
* For
|
|
853
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
801
854
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
802
855
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
803
856
|
* could affect the GC state will have been processed.
|
|
804
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
|
+
*
|
|
805
862
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
806
863
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
807
864
|
*/
|
|
808
|
-
if (this.activeConnection() &&
|
|
809
|
-
this.
|
|
865
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
866
|
+
this.initializeGCStateFromBaseSnapshotP.catch((error) => {});
|
|
810
867
|
}
|
|
811
868
|
}
|
|
812
869
|
|
|
@@ -855,15 +912,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
855
912
|
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
856
913
|
|
|
857
914
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
858
|
-
event.end({ ...gcStats });
|
|
915
|
+
event.end({ ...gcStats, timestamp: currentReferenceTimestampMs });
|
|
859
916
|
this.completedRuns++;
|
|
860
917
|
return gcStats;
|
|
861
918
|
}, { end: true, cancel: "error" });
|
|
862
919
|
}
|
|
863
920
|
|
|
864
921
|
private async runPreGCSteps() {
|
|
865
|
-
// Ensure that
|
|
866
|
-
await this.
|
|
922
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
923
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
867
924
|
// Let the runtime update its pending state before GC runs.
|
|
868
925
|
await this.runtime.updateStateBeforeGC();
|
|
869
926
|
}
|
|
@@ -884,7 +941,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
884
941
|
|
|
885
942
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
886
943
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
887
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
944
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
888
945
|
|
|
889
946
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
890
947
|
// delete these objects here instead.
|
|
@@ -893,7 +950,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
893
950
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
894
951
|
// involving access to deleted data.
|
|
895
952
|
if (this.testMode) {
|
|
896
|
-
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 */);
|
|
897
959
|
}
|
|
898
960
|
|
|
899
961
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
@@ -926,35 +988,76 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
926
988
|
};
|
|
927
989
|
}
|
|
928
990
|
|
|
929
|
-
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;
|
|
930
995
|
|
|
931
996
|
/**
|
|
932
|
-
*
|
|
933
|
-
*
|
|
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.
|
|
934
999
|
*/
|
|
935
1000
|
if (this.trackGCState) {
|
|
936
|
-
this.
|
|
937
|
-
if (
|
|
938
|
-
|
|
939
|
-
this.
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
|
|
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 */);
|
|
953
1020
|
}
|
|
954
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
|
+
}
|
|
955
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`;
|
|
956
1041
|
const builder = new SummaryTreeBuilder();
|
|
957
|
-
|
|
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
|
+
}
|
|
958
1061
|
return builder.getSummaryTree();
|
|
959
1062
|
}
|
|
960
1063
|
|
|
@@ -967,6 +1070,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
967
1070
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
968
1071
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
969
1072
|
sweepEnabled: this.sweepEnabled,
|
|
1073
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
970
1074
|
};
|
|
971
1075
|
}
|
|
972
1076
|
|
|
@@ -996,8 +1100,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
996
1100
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
997
1101
|
this.initialStateNeedsReset = false;
|
|
998
1102
|
if (this.trackGCState) {
|
|
999
|
-
this.
|
|
1000
|
-
this.
|
|
1103
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
1104
|
+
this.pendingSummaryData = undefined;
|
|
1001
1105
|
}
|
|
1002
1106
|
return;
|
|
1003
1107
|
}
|
|
@@ -1012,15 +1116,18 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1012
1116
|
|
|
1013
1117
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
1014
1118
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
1015
|
-
const
|
|
1119
|
+
const latestGCData = await getGCDataFromSnapshot(
|
|
1016
1120
|
gcSnapshotTree,
|
|
1017
1121
|
readAndParseBlob,
|
|
1018
1122
|
);
|
|
1019
|
-
this.
|
|
1123
|
+
this.latestSummaryData = {
|
|
1124
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
1125
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
1126
|
+
};
|
|
1020
1127
|
} else {
|
|
1021
|
-
this.
|
|
1128
|
+
this.latestSummaryData = undefined;
|
|
1022
1129
|
}
|
|
1023
|
-
this.
|
|
1130
|
+
this.pendingSummaryData = undefined;
|
|
1024
1131
|
}
|
|
1025
1132
|
|
|
1026
1133
|
/**
|
|
@@ -1098,6 +1205,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1098
1205
|
currentReferenceTimestampMs: number,
|
|
1099
1206
|
) {
|
|
1100
1207
|
this.previousGCDataFromLastRun = cloneGCData(gcData);
|
|
1208
|
+
this.tombstones = [];
|
|
1101
1209
|
this.newReferencesSinceLastRun.clear();
|
|
1102
1210
|
|
|
1103
1211
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -1130,6 +1238,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1130
1238
|
);
|
|
1131
1239
|
} else {
|
|
1132
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
|
+
}
|
|
1133
1247
|
}
|
|
1134
1248
|
}
|
|
1135
1249
|
}
|
|
@@ -1155,7 +1269,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1155
1269
|
this.newReferencesSinceLastRun,
|
|
1156
1270
|
);
|
|
1157
1271
|
|
|
1158
|
-
if (
|
|
1272
|
+
if (missingExplicitReferences.length > 0) {
|
|
1159
1273
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
1160
1274
|
const event: ITelemetryPerformanceEvent = {
|
|
1161
1275
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -1412,6 +1526,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1412
1526
|
: this.sweepTimeoutMs,
|
|
1413
1527
|
completedGCRuns: this.completedRuns,
|
|
1414
1528
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
1529
|
+
...this.createContainerMetadata,
|
|
1415
1530
|
externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
|
|
1416
1531
|
viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
|
|
1417
1532
|
fromId: fromNodeId,
|
|
@@ -1420,16 +1535,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1420
1535
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
1421
1536
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
1422
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!
|
|
1423
1540
|
if (this.isSummarizerClient) {
|
|
1424
1541
|
this.pendingEventsQueue.push({ ...propsToLog, usageType, state });
|
|
1425
1542
|
} else {
|
|
1426
1543
|
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
1427
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
|
|
1428
1547
|
if (usageType === "Loaded") {
|
|
1429
1548
|
this.mc.logger.sendErrorEvent({
|
|
1430
1549
|
...propsToLog,
|
|
1431
1550
|
eventName: `${state}Object_${usageType}`,
|
|
1432
|
-
pkg: packagePath
|
|
1551
|
+
pkg: packagePathToTelemetryProperty(packagePath),
|
|
1552
|
+
stack: generateStack(),
|
|
1433
1553
|
});
|
|
1434
1554
|
}
|
|
1435
1555
|
|
|
@@ -1443,6 +1563,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1443
1563
|
}
|
|
1444
1564
|
|
|
1445
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
|
|
1446
1571
|
for (const eventProps of this.pendingEventsQueue) {
|
|
1447
1572
|
const { usageType, state, ...propsToLog } = eventProps;
|
|
1448
1573
|
/**
|
|
@@ -1469,15 +1594,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1469
1594
|
}
|
|
1470
1595
|
|
|
1471
1596
|
/**
|
|
1472
|
-
* Gets the garbage collection state from the given snapshot tree.
|
|
1473
|
-
* 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.
|
|
1474
1599
|
*/
|
|
1475
|
-
async function
|
|
1600
|
+
async function getGCDataFromSnapshot(
|
|
1476
1601
|
gcSnapshotTree: ISnapshotTree,
|
|
1477
1602
|
readAndParseBlob: ReadAndParseBlob,
|
|
1478
|
-
): Promise<
|
|
1603
|
+
): Promise<IGCSnapshotData> {
|
|
1479
1604
|
let rootGCState: IGarbageCollectionState = { gcNodes: {} };
|
|
1605
|
+
let tombstones: string[] | undefined;
|
|
1480
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
|
+
|
|
1481
1612
|
// Skip blobs that do not start with the GC prefix.
|
|
1482
1613
|
if (!key.startsWith(gcBlobPrefix)) {
|
|
1483
1614
|
continue;
|
|
@@ -1492,7 +1623,7 @@ async function getGCStateFromSnapshot(
|
|
|
1492
1623
|
// Merge the GC state of this blob into the root GC state.
|
|
1493
1624
|
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1494
1625
|
}
|
|
1495
|
-
return rootGCState;
|
|
1626
|
+
return { gcState: rootGCState, tombstones };
|
|
1496
1627
|
}
|
|
1497
1628
|
|
|
1498
1629
|
function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
|