@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.3.0.115467
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/blobManager.d.ts +20 -5
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +57 -15
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +88 -51
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +205 -300
- 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 +51 -20
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +40 -32
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +227 -161
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +19 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -0
- package/dist/garbageCollectionConstants.js +34 -0
- package/dist/garbageCollectionConstants.js.map +1 -0
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +5 -14
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +30 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -0
- package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -15
- package/dist/opLifecycle/batchManager.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +40 -0
- package/dist/opLifecycle/definitions.d.ts.map +1 -0
- package/dist/opLifecycle/definitions.js +7 -0
- package/dist/opLifecycle/definitions.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +12 -0
- package/dist/opLifecycle/index.d.ts.map +1 -0
- package/dist/opLifecycle/index.js +21 -0
- package/dist/opLifecycle/index.js.map +1 -0
- package/dist/opLifecycle/opCompressor.d.ts +18 -0
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opCompressor.js +53 -0
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/opLifecycle/opDecompressor.d.ts +20 -0
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opDecompressor.js +72 -0
- package/dist/opLifecycle/opDecompressor.js.map +1 -0
- package/dist/opLifecycle/opSplitter.d.ts +17 -0
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
- package/dist/opLifecycle/opSplitter.js +61 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -0
- package/dist/opLifecycle/outbox.d.ts +47 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -0
- package/dist/opLifecycle/outbox.js +153 -0
- package/dist/opLifecycle/outbox.js.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
- package/dist/opLifecycle/remoteMessageProcessor.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 +2 -2
- 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/blobManager.d.ts +20 -5
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +59 -17
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +88 -51
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +203 -297
- 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 -22
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +40 -32
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +220 -154
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +19 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -0
- package/lib/garbageCollectionConstants.js +31 -0
- package/lib/garbageCollectionConstants.js.map +1 -0
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +4 -13
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +6 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -4
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +30 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -0
- package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -15
- package/lib/opLifecycle/batchManager.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +40 -0
- package/lib/opLifecycle/definitions.d.ts.map +1 -0
- package/lib/opLifecycle/definitions.js +6 -0
- package/lib/opLifecycle/definitions.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +12 -0
- package/lib/opLifecycle/index.d.ts.map +1 -0
- package/lib/opLifecycle/index.js +11 -0
- package/lib/opLifecycle/index.js.map +1 -0
- package/lib/opLifecycle/opCompressor.d.ts +18 -0
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opCompressor.js +49 -0
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/opLifecycle/opDecompressor.d.ts +20 -0
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opDecompressor.js +68 -0
- package/lib/opLifecycle/opDecompressor.js.map +1 -0
- package/lib/opLifecycle/opSplitter.d.ts +17 -0
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
- package/lib/opLifecycle/opSplitter.js +57 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -0
- package/lib/opLifecycle/outbox.d.ts +47 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -0
- package/lib/opLifecycle/outbox.js +149 -0
- package/lib/opLifecycle/outbox.js.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
- package/lib/opLifecycle/remoteMessageProcessor.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 +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 +32 -71
- package/prettier.config.cjs +8 -0
- package/src/blobManager.ts +74 -19
- package/src/containerRuntime.ts +286 -369
- package/src/dataStore.ts +13 -1
- package/src/dataStoreContext.ts +100 -76
- package/src/dataStoreContexts.ts +1 -1
- package/src/dataStores.ts +61 -22
- package/src/garbageCollection.ts +282 -163
- package/src/garbageCollectionConstants.ts +35 -0
- package/src/gcSweepReadyUsageDetection.ts +3 -11
- package/src/index.ts +9 -8
- package/src/{batchManager.ts → opLifecycle/batchManager.ts} +42 -28
- package/src/opLifecycle/definitions.ts +44 -0
- package/src/opLifecycle/index.ts +17 -0
- package/src/opLifecycle/opCompressor.ts +64 -0
- package/src/opLifecycle/opDecompressor.ts +84 -0
- package/src/opLifecycle/opSplitter.ts +78 -0
- package/src/opLifecycle/outbox.ts +204 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +90 -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 +5 -3
- package/src/summaryGenerator.ts +3 -2
- package/src/summaryManager.ts +18 -7
- package/dist/batchManager.d.ts +0 -37
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js.map +0 -1
- package/lib/batchManager.d.ts +0 -37
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js.map +0 -1
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,
|
|
@@ -43,6 +45,21 @@ import {
|
|
|
43
45
|
|
|
44
46
|
import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
|
|
45
47
|
import { getSummaryForDatastores } from "./dataStores";
|
|
48
|
+
import {
|
|
49
|
+
defaultInactiveTimeoutMs,
|
|
50
|
+
defaultSessionExpiryDurationMs,
|
|
51
|
+
disableSweepLogKey,
|
|
52
|
+
disableTombstoneKey,
|
|
53
|
+
gcBlobPrefix,
|
|
54
|
+
gcTestModeKey,
|
|
55
|
+
gcTombstoneBlobKey,
|
|
56
|
+
gcTreeKey,
|
|
57
|
+
oneDayMs,
|
|
58
|
+
runGCKey,
|
|
59
|
+
runSessionExpiryKey,
|
|
60
|
+
runSweepKey,
|
|
61
|
+
trackGCStateKey
|
|
62
|
+
} from "./garbageCollectionConstants";
|
|
46
63
|
import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
|
|
47
64
|
import {
|
|
48
65
|
getGCVersion,
|
|
@@ -52,39 +69,12 @@ import {
|
|
|
52
69
|
ReadFluidDataStoreAttributes,
|
|
53
70
|
dataStoreAttributesBlobName,
|
|
54
71
|
IGCMetadata,
|
|
72
|
+
ICreateContainerMetadata,
|
|
55
73
|
} from "./summaryFormat";
|
|
56
74
|
|
|
57
75
|
/** This is the current version of garbage collection. */
|
|
58
76
|
const GCVersion = 1;
|
|
59
77
|
|
|
60
|
-
// The key for the GC tree in summary.
|
|
61
|
-
export const gcTreeKey = "gc";
|
|
62
|
-
// They prefix for GC blobs in the GC tree in summary.
|
|
63
|
-
export const gcBlobPrefix = "__gc";
|
|
64
|
-
|
|
65
|
-
// Feature gate key to turn GC on / off.
|
|
66
|
-
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
67
|
-
// Feature gate key to turn GC sweep on / off.
|
|
68
|
-
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
69
|
-
// Feature gate key to turn GC test mode on / off.
|
|
70
|
-
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
|
-
// Feature gate key to expire a session after a set period of time.
|
|
74
|
-
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
|
-
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
78
|
-
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
79
|
-
// Feature gate key to turn GC sweep log off.
|
|
80
|
-
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
81
|
-
|
|
82
|
-
// One day in milliseconds.
|
|
83
|
-
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
84
|
-
|
|
85
|
-
export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
|
|
86
|
-
export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
|
|
87
|
-
|
|
88
78
|
/** The statistics of the system state after a garbage collection run. */
|
|
89
79
|
export interface IGCStats {
|
|
90
80
|
/** The number of nodes in the container. */
|
|
@@ -127,9 +117,9 @@ export interface IGarbageCollectionRuntime {
|
|
|
127
117
|
/** Returns the garbage collection data of the runtime. */
|
|
128
118
|
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
129
119
|
/** 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
|
-
|
|
120
|
+
updateUsedRoutes(usedRoutes: string[]): void;
|
|
121
|
+
/** After GC has run, called to notify the runtime of routes that are unused in it. */
|
|
122
|
+
updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean): void;
|
|
133
123
|
/** Returns a referenced timestamp to be used to track unreferenced nodes. */
|
|
134
124
|
getCurrentReferenceTimestampMs(): number | undefined;
|
|
135
125
|
/** Returns the type of the GC node. */
|
|
@@ -144,9 +134,9 @@ export interface IGarbageCollector {
|
|
|
144
134
|
readonly shouldRunGC: boolean;
|
|
145
135
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
146
136
|
readonly summaryStateNeedsReset: boolean;
|
|
147
|
-
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
148
|
-
readonly writeDataAtRoot: boolean;
|
|
149
137
|
readonly trackGCState: boolean;
|
|
138
|
+
/** Initialize the state from the base snapshot after its creation. */
|
|
139
|
+
initializeBaseState(): Promise<void>;
|
|
150
140
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
151
141
|
collectGarbage(
|
|
152
142
|
options: { logger?: ITelemetryLogger; runSweep?: boolean; fullGC?: boolean; },
|
|
@@ -184,6 +174,7 @@ export interface IGarbageCollectorCreateParams {
|
|
|
184
174
|
readonly baseLogger: ITelemetryLogger;
|
|
185
175
|
readonly existing: boolean;
|
|
186
176
|
readonly metadata: IContainerRuntimeMetadata | undefined;
|
|
177
|
+
readonly createContainerMetadata: ICreateContainerMetadata;
|
|
187
178
|
readonly baseSnapshot: ISnapshotTree | undefined;
|
|
188
179
|
readonly isSummarizerClient: boolean;
|
|
189
180
|
readonly getNodePackagePath: (nodePath: string) => Promise<readonly string[] | undefined>;
|
|
@@ -191,7 +182,6 @@ export interface IGarbageCollectorCreateParams {
|
|
|
191
182
|
readonly readAndParseBlob: ReadAndParseBlob;
|
|
192
183
|
readonly activeConnection: () => boolean;
|
|
193
184
|
readonly getContainerDiagnosticId: () => string;
|
|
194
|
-
readonly snapshotCacheExpiryMs?: number;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
187
|
/** The state of node that is unreferenced. */
|
|
@@ -221,6 +211,22 @@ interface IUnreferencedEventProps {
|
|
|
221
211
|
viaHandle?: boolean;
|
|
222
212
|
}
|
|
223
213
|
|
|
214
|
+
/**
|
|
215
|
+
* The GC data that is tracked for a summary that is submitted.
|
|
216
|
+
*/
|
|
217
|
+
interface IGCSummaryTrackingData {
|
|
218
|
+
serializedGCState: string | undefined;
|
|
219
|
+
serializedTombstones: string | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The GC data that is read from a snapshot. It contains the GC state and tombstone state.
|
|
224
|
+
*/
|
|
225
|
+
interface IGCSnapshotData {
|
|
226
|
+
gcState: IGarbageCollectionState;
|
|
227
|
+
tombstones: string[] | undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
224
230
|
/**
|
|
225
231
|
* Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
|
|
226
232
|
* be deleted by the sweep phase.
|
|
@@ -377,16 +383,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
377
383
|
public readonly trackGCState: boolean;
|
|
378
384
|
|
|
379
385
|
private readonly testMode: boolean;
|
|
386
|
+
private readonly tombstoneMode: boolean;
|
|
380
387
|
private readonly mc: MonitoringContext;
|
|
381
388
|
|
|
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
389
|
/**
|
|
391
390
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
392
391
|
*
|
|
@@ -408,20 +407,24 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
408
407
|
|
|
409
408
|
// Keeps track of the GC state from the last run.
|
|
410
409
|
private previousGCDataFromLastRun: IGarbageCollectionData | undefined;
|
|
410
|
+
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
411
|
+
// outbound routes from that node.
|
|
412
|
+
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
413
|
+
private tombstones: string[] = [];
|
|
414
|
+
|
|
411
415
|
/**
|
|
412
|
-
* Keeps track of the
|
|
416
|
+
* Keeps track of the GC data from the latest summary successfully submitted to and acked from the server.
|
|
413
417
|
*/
|
|
414
|
-
private
|
|
418
|
+
private latestSummaryData: IGCSummaryTrackingData | undefined;
|
|
415
419
|
/**
|
|
416
|
-
* Keeps track of the
|
|
420
|
+
* Keeps track of the GC data from the last summary submitted to the server but not yet acked.
|
|
417
421
|
*/
|
|
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();
|
|
422
|
+
private pendingSummaryData: IGCSummaryTrackingData | undefined;
|
|
422
423
|
|
|
423
|
-
// Promise when resolved
|
|
424
|
-
private readonly
|
|
424
|
+
// Promise when resolved returns the GC data data in the base snapshot.
|
|
425
|
+
private readonly baseSnapshotDataP: Promise<IGCSnapshotData | undefined>;
|
|
426
|
+
// Promise when resolved initializes the GC state from the data in the base snapshot.
|
|
427
|
+
private readonly initializeGCStateFromBaseSnapshotP: Promise<void>;
|
|
425
428
|
// The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
|
|
426
429
|
private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
|
|
427
430
|
// Map of node ids to their unreferenced state tracker.
|
|
@@ -439,6 +442,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
439
442
|
private completedRuns = 0;
|
|
440
443
|
|
|
441
444
|
private readonly runtime: IGarbageCollectionRuntime;
|
|
445
|
+
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
442
446
|
private readonly gcOptions: IGCRuntimeOptions;
|
|
443
447
|
private readonly isSummarizerClient: boolean;
|
|
444
448
|
|
|
@@ -463,9 +467,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
463
467
|
sweepEnabled: this.sweepEnabled,
|
|
464
468
|
runGC: this.shouldRunGC,
|
|
465
469
|
runSweep: this.shouldRunSweep,
|
|
466
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
467
470
|
testMode: this.testMode,
|
|
471
|
+
tombstoneMode: this.tombstoneMode,
|
|
468
472
|
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
473
|
+
sweepTimeout: this.sweepTimeoutMs,
|
|
469
474
|
inactiveTimeout: this.inactiveTimeoutMs,
|
|
470
475
|
trackGCState: this.trackGCState,
|
|
471
476
|
...this.gcOptions,
|
|
@@ -479,6 +484,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
479
484
|
this.runtime = createParams.runtime;
|
|
480
485
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
481
486
|
this.gcOptions = createParams.gcOptions;
|
|
487
|
+
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
482
488
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
483
489
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
484
490
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -499,6 +505,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
499
505
|
|
|
500
506
|
let prevSummaryGCVersion: number | undefined;
|
|
501
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
510
|
+
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
|
|
511
|
+
*
|
|
512
|
+
* The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
|
|
513
|
+
* The buffer is added to account for any clock skew or other edge cases.
|
|
514
|
+
* We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
|
|
515
|
+
*/
|
|
516
|
+
function computeSweepTimeout(sessionExpiryTimeoutMs: number | undefined) {
|
|
517
|
+
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
518
|
+
const bufferMs = oneDayMs;
|
|
519
|
+
return sessionExpiryTimeoutMs &&
|
|
520
|
+
(sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
521
|
+
}
|
|
522
|
+
|
|
502
523
|
/**
|
|
503
524
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
504
525
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -513,6 +534,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
513
534
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
514
535
|
this.sweepEnabled = metadata?.sweepEnabled ?? false;
|
|
515
536
|
this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
|
|
537
|
+
this.sweepTimeoutMs =
|
|
538
|
+
metadata?.sweepTimeoutMs
|
|
539
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
|
|
516
540
|
} else {
|
|
517
541
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
518
542
|
// scenario but explicitly failing makes it clearer and promotes correct usage.
|
|
@@ -520,20 +544,28 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
520
544
|
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
521
545
|
}
|
|
522
546
|
|
|
547
|
+
// This Test Override only applies for new containers
|
|
548
|
+
const testOverrideSweepTimeoutMs =
|
|
549
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
550
|
+
|
|
523
551
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
524
552
|
// flag in GC options to false.
|
|
525
553
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
526
554
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
527
|
-
|
|
555
|
+
// ...unless we're using the TestOverride
|
|
556
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
528
557
|
|
|
529
558
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
530
559
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
531
560
|
this.sessionExpiryTimeoutMs = this.gcOptions.sessionExpiryTimeoutMs ?? defaultSessionExpiryDurationMs;
|
|
532
561
|
}
|
|
562
|
+
this.sweepTimeoutMs =
|
|
563
|
+
testOverrideSweepTimeoutMs
|
|
564
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
533
565
|
}
|
|
534
566
|
|
|
535
567
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
536
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
568
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
537
569
|
// If Test Override config is set, override Session Expiry timeout.
|
|
538
570
|
const overrideSessionExpiryTimeoutMs =
|
|
539
571
|
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
@@ -544,21 +576,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
544
576
|
() => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); },
|
|
545
577
|
);
|
|
546
578
|
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
579
|
}
|
|
563
580
|
|
|
564
581
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -585,16 +602,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
585
602
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
586
603
|
*
|
|
587
604
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
588
|
-
*
|
|
589
605
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
590
|
-
*
|
|
591
|
-
*
|
|
606
|
+
* 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
|
|
607
|
+
* the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
|
|
608
|
+
* 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
592
609
|
* feature flag.
|
|
593
610
|
*/
|
|
594
|
-
this.shouldRunSweep =
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
this.shouldRunSweep =
|
|
612
|
+
this.shouldRunGC
|
|
613
|
+
&& this.sweepTimeoutMs !== undefined
|
|
614
|
+
&& (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
|
|
598
615
|
|
|
599
616
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
600
617
|
|
|
@@ -610,20 +627,17 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
610
627
|
|
|
611
628
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
612
629
|
this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? this.gcOptions.runGCInTestMode === true;
|
|
630
|
+
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
631
|
+
this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
613
632
|
|
|
614
|
-
// GC state
|
|
615
|
-
|
|
633
|
+
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
634
|
+
// contain GC tree and GC is enabled.
|
|
635
|
+
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
636
|
+
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
616
637
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
621
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
|
|
625
|
-
// this once since it involves fetching blobs from storage which is expensive.
|
|
626
|
-
const baseSummaryStateP = new LazyPromise<IGarbageCollectionState | undefined>(async () => {
|
|
638
|
+
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
639
|
+
// it involves fetching blobs from storage which is expensive.
|
|
640
|
+
this.baseSnapshotDataP = new LazyPromise<IGCSnapshotData | undefined>(async () => {
|
|
627
641
|
if (baseSnapshot === undefined) {
|
|
628
642
|
return undefined;
|
|
629
643
|
}
|
|
@@ -632,16 +646,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
632
646
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
633
647
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
634
648
|
if (gcSnapshotTree !== undefined) {
|
|
635
|
-
|
|
636
|
-
this._writeDataAtRoot = true;
|
|
637
|
-
const baseGCState = await getGCStateFromSnapshot(
|
|
649
|
+
return getGCDataFromSnapshot(
|
|
638
650
|
gcSnapshotTree,
|
|
639
651
|
readAndParseBlob,
|
|
640
652
|
);
|
|
641
|
-
if (this.trackGCState) {
|
|
642
|
-
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
643
|
-
}
|
|
644
|
-
return baseGCState;
|
|
645
653
|
}
|
|
646
654
|
|
|
647
655
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
@@ -685,7 +693,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
685
693
|
}
|
|
686
694
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
687
695
|
// 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;
|
|
696
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
|
|
689
697
|
} catch (error) {
|
|
690
698
|
const dpe = DataProcessingError.wrapIfUnrecognized(
|
|
691
699
|
error,
|
|
@@ -697,11 +705,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
697
705
|
});
|
|
698
706
|
|
|
699
707
|
/**
|
|
700
|
-
* Set up the initializer which initializes the
|
|
701
|
-
*
|
|
702
|
-
*
|
|
708
|
+
* Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
|
|
709
|
+
* connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
|
|
710
|
+
* GC state and updates their inactive or sweep ready state.
|
|
703
711
|
*/
|
|
704
|
-
this.
|
|
712
|
+
this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
|
|
705
713
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
706
714
|
/**
|
|
707
715
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
@@ -720,19 +728,19 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
720
728
|
return;
|
|
721
729
|
}
|
|
722
730
|
|
|
723
|
-
const
|
|
731
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
724
732
|
/**
|
|
725
|
-
* The base
|
|
733
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
726
734
|
* 1. The first summary created by the detached container.
|
|
727
735
|
* 2. A summary that was generated with GC disabled.
|
|
728
736
|
* 3. A summary that was generated before GC even existed.
|
|
729
737
|
*/
|
|
730
|
-
if (
|
|
738
|
+
if (baseSnapshotData === undefined) {
|
|
731
739
|
return;
|
|
732
740
|
}
|
|
733
741
|
|
|
734
742
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
735
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
743
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
736
744
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
737
745
|
this.unreferencedNodesState.set(
|
|
738
746
|
nodeId,
|
|
@@ -747,18 +755,26 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
747
755
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
748
756
|
}
|
|
749
757
|
this.previousGCDataFromLastRun = { gcNodes };
|
|
758
|
+
|
|
759
|
+
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
760
|
+
if (this.trackGCState) {
|
|
761
|
+
this.latestSummaryData = {
|
|
762
|
+
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
763
|
+
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
764
|
+
};
|
|
765
|
+
}
|
|
750
766
|
});
|
|
751
767
|
|
|
752
768
|
// Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
|
|
753
769
|
// which the caller uses to initialize each node's GC state.
|
|
754
770
|
this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
|
|
755
|
-
const
|
|
756
|
-
if (
|
|
771
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
772
|
+
if (baseSnapshotData === undefined) {
|
|
757
773
|
return new Map();
|
|
758
774
|
}
|
|
759
775
|
|
|
760
776
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
761
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
777
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
762
778
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
763
779
|
}
|
|
764
780
|
// Run GC on the nodes in the base summary to get the routes used in each node in the container.
|
|
@@ -769,7 +785,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
769
785
|
const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
|
|
770
786
|
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
771
787
|
// 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(
|
|
788
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
773
789
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
774
790
|
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
775
791
|
if (dataStoreGCDetails !== undefined) {
|
|
@@ -790,6 +806,27 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
790
806
|
}
|
|
791
807
|
}
|
|
792
808
|
|
|
809
|
+
/**
|
|
810
|
+
* Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
|
|
811
|
+
* before they are loaded or used. This is important to get accurate information of whether tombstoned object are
|
|
812
|
+
* in use or not.
|
|
813
|
+
*/
|
|
814
|
+
public async initializeBaseState(): Promise<void> {
|
|
815
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
816
|
+
/**
|
|
817
|
+
* The base snapshot data or tombstone state will not be present if the container is loaded from:
|
|
818
|
+
* 1. The first summary created by the detached container.
|
|
819
|
+
* 2. A summary that was generated with GC disabled.
|
|
820
|
+
* 3. A summary that was generated before GC even existed.
|
|
821
|
+
* 4. A summary that was generated with tombstone feature disabled.
|
|
822
|
+
*/
|
|
823
|
+
if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.tombstones = baseSnapshotData.tombstones;
|
|
827
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
828
|
+
}
|
|
829
|
+
|
|
793
830
|
/**
|
|
794
831
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
795
832
|
* to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
|
|
@@ -798,16 +835,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
798
835
|
*/
|
|
799
836
|
public setConnectionState(connected: boolean, clientId?: string | undefined): void {
|
|
800
837
|
/**
|
|
801
|
-
* For
|
|
838
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
802
839
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
803
840
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
804
841
|
* could affect the GC state will have been processed.
|
|
805
842
|
*
|
|
843
|
+
* If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
|
|
844
|
+
* InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
|
|
845
|
+
* the receiving summarizer client.
|
|
846
|
+
*
|
|
806
847
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
807
848
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
808
849
|
*/
|
|
809
|
-
if (this.activeConnection() &&
|
|
810
|
-
this.
|
|
850
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
851
|
+
this.initializeGCStateFromBaseSnapshotP.catch((error) => {});
|
|
811
852
|
}
|
|
812
853
|
}
|
|
813
854
|
|
|
@@ -856,15 +897,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
856
897
|
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
857
898
|
|
|
858
899
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
859
|
-
event.end({ ...gcStats });
|
|
900
|
+
event.end({ ...gcStats, timestamp: currentReferenceTimestampMs });
|
|
860
901
|
this.completedRuns++;
|
|
861
902
|
return gcStats;
|
|
862
903
|
}, { end: true, cancel: "error" });
|
|
863
904
|
}
|
|
864
905
|
|
|
865
906
|
private async runPreGCSteps() {
|
|
866
|
-
// Ensure that
|
|
867
|
-
await this.
|
|
907
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
908
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
868
909
|
// Let the runtime update its pending state before GC runs.
|
|
869
910
|
await this.runtime.updateStateBeforeGC();
|
|
870
911
|
}
|
|
@@ -885,7 +926,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
885
926
|
|
|
886
927
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
887
928
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
888
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
929
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
889
930
|
|
|
890
931
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
891
932
|
// delete these objects here instead.
|
|
@@ -894,7 +935,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
894
935
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
895
936
|
// involving access to deleted data.
|
|
896
937
|
if (this.testMode) {
|
|
897
|
-
this.runtime.
|
|
938
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
|
|
939
|
+
} else if (this.tombstoneMode) {
|
|
940
|
+
// If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
|
|
941
|
+
// scenarios involving access to "deleted" data without actually deleting the data from summaries.
|
|
942
|
+
// Note: we will not tombstone in test mode
|
|
943
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
898
944
|
}
|
|
899
945
|
|
|
900
946
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
@@ -927,35 +973,76 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
927
973
|
};
|
|
928
974
|
}
|
|
929
975
|
|
|
930
|
-
const
|
|
976
|
+
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
977
|
+
const serializedTombstones = this.tombstoneMode
|
|
978
|
+
? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
|
|
979
|
+
: undefined;
|
|
931
980
|
|
|
932
981
|
/**
|
|
933
|
-
*
|
|
934
|
-
*
|
|
982
|
+
* Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
|
|
983
|
+
* summary, send summary handles for them. Otherwise, send the data in summary blobs.
|
|
935
984
|
*/
|
|
936
985
|
if (this.trackGCState) {
|
|
937
|
-
this.
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
this.
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
986
|
+
this.pendingSummaryData = { serializedGCState, serializedTombstones };
|
|
987
|
+
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
988
|
+
// If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
|
|
989
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
990
|
+
&& this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
991
|
+
const stats = mergeStats();
|
|
992
|
+
stats.handleNodeCount++;
|
|
993
|
+
return {
|
|
994
|
+
summary: {
|
|
995
|
+
type: SummaryType.Handle,
|
|
996
|
+
handle: `/${gcTreeKey}`,
|
|
997
|
+
handleType: SummaryType.Tree,
|
|
998
|
+
},
|
|
999
|
+
stats,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// If either or both of GC state or tombstone state changed, build a GC summary tree.
|
|
1004
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
954
1005
|
}
|
|
955
1006
|
}
|
|
1007
|
+
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
1008
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
1009
|
+
}
|
|
956
1010
|
|
|
1011
|
+
/**
|
|
1012
|
+
* Builds the GC summary tree which contains GC state and tombstone state.
|
|
1013
|
+
* If trackState is false, both GC state and tombstone state are written as summary blobs.
|
|
1014
|
+
* If trackState is true, summary blob is written for GC state or tombstone state if they changed.
|
|
1015
|
+
* @param serializedGCState - The GC state serialized as string.
|
|
1016
|
+
* @param serializedTombstones - THe tombstone state serialized as string.
|
|
1017
|
+
* @param trackState - Whether we are tracking GC state across summaries.
|
|
1018
|
+
* @returns the GC summary tree.
|
|
1019
|
+
*/
|
|
1020
|
+
private buildGCSummaryTree(
|
|
1021
|
+
serializedGCState: string,
|
|
1022
|
+
serializedTombstones: string | undefined,
|
|
1023
|
+
trackState: boolean,
|
|
1024
|
+
): ISummaryTreeWithStats {
|
|
1025
|
+
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
957
1026
|
const builder = new SummaryTreeBuilder();
|
|
958
|
-
|
|
1027
|
+
|
|
1028
|
+
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
1029
|
+
if (this.latestSummaryData?.serializedGCState === serializedGCState && trackState) {
|
|
1030
|
+
builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
|
|
1031
|
+
} else {
|
|
1032
|
+
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// If there is no tombstone data, return only the GC state.
|
|
1036
|
+
if (serializedTombstones === undefined) {
|
|
1037
|
+
return builder.getSummaryTree();
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
1041
|
+
if (this.latestSummaryData?.serializedTombstones === serializedTombstones && trackState) {
|
|
1042
|
+
builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
|
|
1043
|
+
} else {
|
|
1044
|
+
builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
|
|
1045
|
+
}
|
|
959
1046
|
return builder.getSummaryTree();
|
|
960
1047
|
}
|
|
961
1048
|
|
|
@@ -968,6 +1055,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
968
1055
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
969
1056
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
970
1057
|
sweepEnabled: this.sweepEnabled,
|
|
1058
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
971
1059
|
};
|
|
972
1060
|
}
|
|
973
1061
|
|
|
@@ -997,8 +1085,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
997
1085
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
998
1086
|
this.initialStateNeedsReset = false;
|
|
999
1087
|
if (this.trackGCState) {
|
|
1000
|
-
this.
|
|
1001
|
-
this.
|
|
1088
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
1089
|
+
this.pendingSummaryData = undefined;
|
|
1002
1090
|
}
|
|
1003
1091
|
return;
|
|
1004
1092
|
}
|
|
@@ -1013,15 +1101,18 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1013
1101
|
|
|
1014
1102
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
1015
1103
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
1016
|
-
const
|
|
1104
|
+
const latestGCData = await getGCDataFromSnapshot(
|
|
1017
1105
|
gcSnapshotTree,
|
|
1018
1106
|
readAndParseBlob,
|
|
1019
1107
|
);
|
|
1020
|
-
this.
|
|
1108
|
+
this.latestSummaryData = {
|
|
1109
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
1110
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
1111
|
+
};
|
|
1021
1112
|
} else {
|
|
1022
|
-
this.
|
|
1113
|
+
this.latestSummaryData = undefined;
|
|
1023
1114
|
}
|
|
1024
|
-
this.
|
|
1115
|
+
this.pendingSummaryData = undefined;
|
|
1025
1116
|
}
|
|
1026
1117
|
|
|
1027
1118
|
/**
|
|
@@ -1099,6 +1190,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1099
1190
|
currentReferenceTimestampMs: number,
|
|
1100
1191
|
) {
|
|
1101
1192
|
this.previousGCDataFromLastRun = cloneGCData(gcData);
|
|
1193
|
+
this.tombstones = [];
|
|
1102
1194
|
this.newReferencesSinceLastRun.clear();
|
|
1103
1195
|
|
|
1104
1196
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -1131,14 +1223,25 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1131
1223
|
);
|
|
1132
1224
|
} else {
|
|
1133
1225
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
1226
|
+
if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
|
|
1227
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
1228
|
+
if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
|
|
1229
|
+
this.tombstones.push(nodeId);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1134
1232
|
}
|
|
1135
1233
|
}
|
|
1136
1234
|
}
|
|
1137
1235
|
|
|
1138
1236
|
/**
|
|
1139
1237
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
1140
|
-
* time.
|
|
1141
|
-
*
|
|
1238
|
+
* time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
|
|
1239
|
+
* updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
|
|
1240
|
+
* these objects while there can be in-memory referenced to it:
|
|
1241
|
+
* 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
|
|
1242
|
+
* added, the object may have been accessed and in-memory reference to it added.
|
|
1243
|
+
* 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
|
|
1244
|
+
* unreferenced, they could have been accessed and in-memory reference to them added.
|
|
1142
1245
|
*
|
|
1143
1246
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
1144
1247
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
@@ -1156,7 +1259,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1156
1259
|
this.newReferencesSinceLastRun,
|
|
1157
1260
|
);
|
|
1158
1261
|
|
|
1159
|
-
if (
|
|
1262
|
+
if (missingExplicitReferences.length > 0) {
|
|
1160
1263
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
1161
1264
|
const event: ITelemetryPerformanceEvent = {
|
|
1162
1265
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -1179,41 +1282,41 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1179
1282
|
* run, and then add the references since last run.
|
|
1180
1283
|
*
|
|
1181
1284
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
1182
|
-
*
|
|
1183
1285
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
1184
|
-
* references added new outbound references before
|
|
1286
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
1185
1287
|
*
|
|
1186
1288
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
1187
|
-
* references added new outbound references before
|
|
1289
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
1188
1290
|
*
|
|
1189
1291
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
1190
|
-
*
|
|
1191
|
-
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
1192
|
-
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
1193
|
-
*
|
|
1292
|
+
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
1194
1293
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
1195
1294
|
*/
|
|
1196
1295
|
const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
|
|
1296
|
+
const newOutboundRoutesSinceLastRun: string[] = [];
|
|
1197
1297
|
this.newReferencesSinceLastRun.forEach((outboundRoutes: string[], sourceNodeId: string) => {
|
|
1198
1298
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
1199
1299
|
gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
|
|
1200
1300
|
} else {
|
|
1201
1301
|
gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
|
|
1202
1302
|
}
|
|
1303
|
+
newOutboundRoutesSinceLastRun.push(...outboundRoutes);
|
|
1203
1304
|
});
|
|
1204
1305
|
|
|
1205
1306
|
/**
|
|
1206
|
-
* Run GC on the above reference graph
|
|
1307
|
+
* Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
|
|
1308
|
+
* list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
|
|
1207
1309
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
1208
|
-
*
|
|
1310
|
+
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
1311
|
+
* unreferenced and add unreferenced state.
|
|
1209
1312
|
*/
|
|
1210
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
|
|
1313
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
|
|
1211
1314
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
1212
1315
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
1213
1316
|
if (nodeStateTracker !== undefined) {
|
|
1214
1317
|
// Stop tracking so as to clear out any running timers.
|
|
1215
1318
|
nodeStateTracker.stopTracking();
|
|
1216
|
-
// Delete the
|
|
1319
|
+
// Delete the unreferenced state as we don't need to track it any more.
|
|
1217
1320
|
this.unreferencedNodesState.delete(nodeId);
|
|
1218
1321
|
}
|
|
1219
1322
|
}
|
|
@@ -1413,6 +1516,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1413
1516
|
: this.sweepTimeoutMs,
|
|
1414
1517
|
completedGCRuns: this.completedRuns,
|
|
1415
1518
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
1519
|
+
...this.createContainerMetadata,
|
|
1416
1520
|
externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
|
|
1417
1521
|
viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
|
|
1418
1522
|
fromId: fromNodeId,
|
|
@@ -1421,16 +1525,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1421
1525
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
1422
1526
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
1423
1527
|
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
1528
|
+
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
1529
|
+
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
1424
1530
|
if (this.isSummarizerClient) {
|
|
1425
1531
|
this.pendingEventsQueue.push({ ...propsToLog, usageType, state });
|
|
1426
1532
|
} else {
|
|
1427
1533
|
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
1428
1534
|
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
1535
|
+
// Events generated:
|
|
1536
|
+
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1429
1537
|
if (usageType === "Loaded") {
|
|
1430
1538
|
this.mc.logger.sendErrorEvent({
|
|
1431
1539
|
...propsToLog,
|
|
1432
1540
|
eventName: `${state}Object_${usageType}`,
|
|
1433
|
-
pkg: packagePath
|
|
1541
|
+
pkg: packagePathToTelemetryProperty(packagePath),
|
|
1434
1542
|
stack: generateStack(),
|
|
1435
1543
|
});
|
|
1436
1544
|
}
|
|
@@ -1445,6 +1553,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1445
1553
|
}
|
|
1446
1554
|
|
|
1447
1555
|
private async logUnreferencedEvents(logger: ITelemetryLogger) {
|
|
1556
|
+
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
1557
|
+
// summary time they are then logged.
|
|
1558
|
+
// Events generated:
|
|
1559
|
+
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
1560
|
+
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
1448
1561
|
for (const eventProps of this.pendingEventsQueue) {
|
|
1449
1562
|
const { usageType, state, ...propsToLog } = eventProps;
|
|
1450
1563
|
/**
|
|
@@ -1471,15 +1584,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1471
1584
|
}
|
|
1472
1585
|
|
|
1473
1586
|
/**
|
|
1474
|
-
* Gets the garbage collection state from the given snapshot tree.
|
|
1475
|
-
* Merge the GC state from all such blobs
|
|
1587
|
+
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1588
|
+
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1476
1589
|
*/
|
|
1477
|
-
async function
|
|
1590
|
+
async function getGCDataFromSnapshot(
|
|
1478
1591
|
gcSnapshotTree: ISnapshotTree,
|
|
1479
1592
|
readAndParseBlob: ReadAndParseBlob,
|
|
1480
|
-
): Promise<
|
|
1593
|
+
): Promise<IGCSnapshotData> {
|
|
1481
1594
|
let rootGCState: IGarbageCollectionState = { gcNodes: {} };
|
|
1595
|
+
let tombstones: string[] | undefined;
|
|
1482
1596
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1597
|
+
if (key === gcTombstoneBlobKey) {
|
|
1598
|
+
tombstones = await readAndParseBlob<string[]>(gcSnapshotTree.blobs[key]);
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1483
1602
|
// Skip blobs that do not start with the GC prefix.
|
|
1484
1603
|
if (!key.startsWith(gcBlobPrefix)) {
|
|
1485
1604
|
continue;
|
|
@@ -1494,7 +1613,7 @@ async function getGCStateFromSnapshot(
|
|
|
1494
1613
|
// Merge the GC state of this blob into the root GC state.
|
|
1495
1614
|
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1496
1615
|
}
|
|
1497
|
-
return rootGCState;
|
|
1616
|
+
return { gcState: rootGCState, tombstones };
|
|
1498
1617
|
}
|
|
1499
1618
|
|
|
1500
1619
|
function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
|