@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/lib/garbageCollection.js
CHANGED
|
@@ -18,38 +18,15 @@ import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@flu
|
|
|
18
18
|
import { cloneGCData, concatGarbageCollectionStates, concatGarbageCollectionData, runGarbageCollection, unpackChildNodesGCDetails, } from "@fluidframework/garbage-collector";
|
|
19
19
|
import { SummaryType } from "@fluidframework/protocol-definitions";
|
|
20
20
|
import { gcBlobKey, } from "@fluidframework/runtime-definitions";
|
|
21
|
-
import { mergeStats, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
|
|
21
|
+
import { mergeStats, packagePathToTelemetryProperty, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
|
|
22
22
|
import { ChildLogger, generateStack, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
|
|
23
23
|
import { RuntimeHeaders } from "./containerRuntime";
|
|
24
24
|
import { getSummaryForDatastores } from "./dataStores";
|
|
25
|
+
import { defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcBlobPrefix, gcTestModeKey, gcTombstoneBlobKey, gcTreeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, trackGCStateKey } from "./garbageCollectionConstants";
|
|
25
26
|
import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
|
|
26
27
|
import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
|
|
27
28
|
/** This is the current version of garbage collection. */
|
|
28
29
|
const GCVersion = 1;
|
|
29
|
-
// The key for the GC tree in summary.
|
|
30
|
-
export const gcTreeKey = "gc";
|
|
31
|
-
// They prefix for GC blobs in the GC tree in summary.
|
|
32
|
-
export const gcBlobPrefix = "__gc";
|
|
33
|
-
// Feature gate key to turn GC on / off.
|
|
34
|
-
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
35
|
-
// Feature gate key to turn GC sweep on / off.
|
|
36
|
-
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
37
|
-
// Feature gate key to turn GC test mode on / off.
|
|
38
|
-
export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
39
|
-
// Feature gate key to write GC data at the root of the summary tree.
|
|
40
|
-
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
41
|
-
// Feature gate key to expire a session after a set period of time.
|
|
42
|
-
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
43
|
-
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
|
|
44
|
-
export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
45
|
-
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
46
|
-
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
47
|
-
// Feature gate key to turn GC sweep log off.
|
|
48
|
-
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
49
|
-
// One day in milliseconds.
|
|
50
|
-
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
51
|
-
export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
|
|
52
|
-
export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
|
|
53
30
|
/** The types of GC nodes in the GC reference graph. */
|
|
54
31
|
export const GCNodeType = {
|
|
55
32
|
// Nodes that are for data stores.
|
|
@@ -162,10 +139,6 @@ export class UnreferencedStateTracker {
|
|
|
162
139
|
export class GarbageCollector {
|
|
163
140
|
constructor(createParams) {
|
|
164
141
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
165
|
-
/**
|
|
166
|
-
* Tells whether the GC data should be written to the root of the summary tree.
|
|
167
|
-
*/
|
|
168
|
-
this._writeDataAtRoot = true;
|
|
169
142
|
/**
|
|
170
143
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
171
144
|
*
|
|
@@ -184,6 +157,7 @@ export class GarbageCollector {
|
|
|
184
157
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
185
158
|
// outbound routes from that node.
|
|
186
159
|
this.newReferencesSinceLastRun = new Map();
|
|
160
|
+
this.tombstones = [];
|
|
187
161
|
// Map of node ids to their unreferenced state tracker.
|
|
188
162
|
this.unreferencedNodesState = new Map();
|
|
189
163
|
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
@@ -196,6 +170,7 @@ export class GarbageCollector {
|
|
|
196
170
|
this.runtime = createParams.runtime;
|
|
197
171
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
198
172
|
this.gcOptions = createParams.gcOptions;
|
|
173
|
+
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
199
174
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
200
175
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
201
176
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -205,6 +180,20 @@ export class GarbageCollector {
|
|
|
205
180
|
this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
|
|
206
181
|
this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
207
182
|
let prevSummaryGCVersion;
|
|
183
|
+
/**
|
|
184
|
+
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
185
|
+
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
|
|
186
|
+
*
|
|
187
|
+
* The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
|
|
188
|
+
* The buffer is added to account for any clock skew or other edge cases.
|
|
189
|
+
* We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
|
|
190
|
+
*/
|
|
191
|
+
function computeSweepTimeout(sessionExpiryTimeoutMs) {
|
|
192
|
+
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
193
|
+
const bufferMs = oneDayMs;
|
|
194
|
+
return sessionExpiryTimeoutMs &&
|
|
195
|
+
(sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
196
|
+
}
|
|
208
197
|
/**
|
|
209
198
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
210
199
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -219,6 +208,8 @@ export class GarbageCollector {
|
|
|
219
208
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
220
209
|
this.sweepEnabled = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.sweepEnabled) !== null && _a !== void 0 ? _a : false;
|
|
221
210
|
this.sessionExpiryTimeoutMs = metadata === null || metadata === void 0 ? void 0 : metadata.sessionExpiryTimeoutMs;
|
|
211
|
+
this.sweepTimeoutMs =
|
|
212
|
+
(_b = metadata === null || metadata === void 0 ? void 0 : metadata.sweepTimeoutMs) !== null && _b !== void 0 ? _b : computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
|
|
222
213
|
}
|
|
223
214
|
else {
|
|
224
215
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
@@ -226,36 +217,28 @@ export class GarbageCollector {
|
|
|
226
217
|
if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
|
|
227
218
|
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
228
219
|
}
|
|
220
|
+
// This Test Override only applies for new containers
|
|
221
|
+
const testOverrideSweepTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
229
222
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
230
223
|
// flag in GC options to false.
|
|
231
224
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
232
225
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
233
|
-
|
|
226
|
+
// ...unless we're using the TestOverride
|
|
227
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
234
228
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
235
229
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
236
|
-
this.sessionExpiryTimeoutMs = (
|
|
230
|
+
this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
|
|
237
231
|
}
|
|
232
|
+
this.sweepTimeoutMs =
|
|
233
|
+
testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
238
234
|
}
|
|
239
235
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
240
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
236
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
241
237
|
// If Test Override config is set, override Session Expiry timeout.
|
|
242
238
|
const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
243
239
|
const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
|
|
244
240
|
this.sessionExpiryTimer = new Timer(timeoutMs, () => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
|
|
245
241
|
this.sessionExpiryTimer.start();
|
|
246
|
-
// TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
|
|
247
|
-
// This unblocks the Sweep Log (see logSweepEvents function).
|
|
248
|
-
// This will be removed before sweep is fully implemented.
|
|
249
|
-
const snapshotCacheExpiryMs = (_c = createParams.snapshotCacheExpiryMs) !== null && _c !== void 0 ? _c : 5 * 24 * 60 * 60 * 1000;
|
|
250
|
-
/**
|
|
251
|
-
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
252
|
-
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
|
|
253
|
-
* added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
|
|
254
|
-
* but make it one day to be safe.
|
|
255
|
-
*/
|
|
256
|
-
if (snapshotCacheExpiryMs !== undefined) {
|
|
257
|
-
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + oneDayMs;
|
|
258
|
-
}
|
|
259
242
|
}
|
|
260
243
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
261
244
|
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
@@ -278,36 +261,34 @@ export class GarbageCollector {
|
|
|
278
261
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
279
262
|
*
|
|
280
263
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
281
|
-
*
|
|
282
264
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
283
|
-
*
|
|
284
|
-
*
|
|
265
|
+
* 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
|
|
266
|
+
* the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
|
|
267
|
+
* 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
285
268
|
* feature flag.
|
|
286
269
|
*/
|
|
287
|
-
this.shouldRunSweep =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
270
|
+
this.shouldRunSweep =
|
|
271
|
+
this.shouldRunGC
|
|
272
|
+
&& this.sweepTimeoutMs !== undefined
|
|
273
|
+
&& ((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
|
|
291
274
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
292
275
|
// Override inactive timeout if test config or gc options to override it is set.
|
|
293
|
-
this.inactiveTimeoutMs = (
|
|
276
|
+
this.inactiveTimeoutMs = (_g = (_f = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _f !== void 0 ? _f : this.gcOptions.inactiveTimeoutMs) !== null && _g !== void 0 ? _g : defaultInactiveTimeoutMs;
|
|
294
277
|
// Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
|
|
295
278
|
if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
|
|
296
279
|
throw new UsageError("inactive timeout should not be greater than the sweep timeout");
|
|
297
280
|
}
|
|
298
281
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
299
|
-
this.testMode = (
|
|
300
|
-
//
|
|
301
|
-
this.
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// this once since it involves fetching blobs from storage which is expensive.
|
|
310
|
-
const baseSummaryStateP = new LazyPromise(async () => {
|
|
282
|
+
this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
|
|
283
|
+
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
284
|
+
this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
285
|
+
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
286
|
+
// contain GC tree and GC is enabled.
|
|
287
|
+
const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
|
|
288
|
+
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
289
|
+
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
290
|
+
// it involves fetching blobs from storage which is expensive.
|
|
291
|
+
this.baseSnapshotDataP = new LazyPromise(async () => {
|
|
311
292
|
var _a;
|
|
312
293
|
if (baseSnapshot === undefined) {
|
|
313
294
|
return undefined;
|
|
@@ -316,13 +297,7 @@ export class GarbageCollector {
|
|
|
316
297
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
317
298
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
318
299
|
if (gcSnapshotTree !== undefined) {
|
|
319
|
-
|
|
320
|
-
this._writeDataAtRoot = true;
|
|
321
|
-
const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
322
|
-
if (this.trackGCState) {
|
|
323
|
-
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
324
|
-
}
|
|
325
|
-
return baseGCState;
|
|
300
|
+
return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
326
301
|
}
|
|
327
302
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
328
303
|
// consolidate into IGarbageCollectionState format.
|
|
@@ -358,7 +333,7 @@ export class GarbageCollector {
|
|
|
358
333
|
}
|
|
359
334
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
360
335
|
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
361
|
-
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
336
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
|
|
362
337
|
}
|
|
363
338
|
catch (error) {
|
|
364
339
|
const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
@@ -367,11 +342,11 @@ export class GarbageCollector {
|
|
|
367
342
|
}
|
|
368
343
|
});
|
|
369
344
|
/**
|
|
370
|
-
* Set up the initializer which initializes the
|
|
371
|
-
*
|
|
372
|
-
*
|
|
345
|
+
* Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
|
|
346
|
+
* connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
|
|
347
|
+
* GC state and updates their inactive or sweep ready state.
|
|
373
348
|
*/
|
|
374
|
-
this.
|
|
349
|
+
this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
|
|
375
350
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
376
351
|
/**
|
|
377
352
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
@@ -389,34 +364,41 @@ export class GarbageCollector {
|
|
|
389
364
|
});
|
|
390
365
|
return;
|
|
391
366
|
}
|
|
392
|
-
const
|
|
367
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
393
368
|
/**
|
|
394
|
-
* The base
|
|
369
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
395
370
|
* 1. The first summary created by the detached container.
|
|
396
371
|
* 2. A summary that was generated with GC disabled.
|
|
397
372
|
* 3. A summary that was generated before GC even existed.
|
|
398
373
|
*/
|
|
399
|
-
if (
|
|
374
|
+
if (baseSnapshotData === undefined) {
|
|
400
375
|
return;
|
|
401
376
|
}
|
|
402
377
|
const gcNodes = {};
|
|
403
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
378
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
404
379
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
405
380
|
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
406
381
|
}
|
|
407
382
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
408
383
|
}
|
|
409
384
|
this.previousGCDataFromLastRun = { gcNodes };
|
|
385
|
+
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
386
|
+
if (this.trackGCState) {
|
|
387
|
+
this.latestSummaryData = {
|
|
388
|
+
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
389
|
+
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
410
392
|
});
|
|
411
393
|
// Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
|
|
412
394
|
// which the caller uses to initialize each node's GC state.
|
|
413
395
|
this.baseGCDetailsP = new LazyPromise(async () => {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
396
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
397
|
+
if (baseSnapshotData === undefined) {
|
|
416
398
|
return new Map();
|
|
417
399
|
}
|
|
418
400
|
const gcNodes = {};
|
|
419
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
401
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
420
402
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
421
403
|
}
|
|
422
404
|
// Run GC on the nodes in the base summary to get the routes used in each node in the container.
|
|
@@ -426,7 +408,7 @@ export class GarbageCollector {
|
|
|
426
408
|
const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
|
|
427
409
|
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
428
410
|
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
429
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
411
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
430
412
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
431
413
|
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
432
414
|
if (dataStoreGCDetails !== undefined) {
|
|
@@ -465,12 +447,29 @@ export class GarbageCollector {
|
|
|
465
447
|
return this.initialStateNeedsReset ||
|
|
466
448
|
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
|
|
467
449
|
}
|
|
468
|
-
get writeDataAtRoot() {
|
|
469
|
-
return this._writeDataAtRoot;
|
|
470
|
-
}
|
|
471
450
|
/** Returns a list of all the configurations for garbage collection. */
|
|
472
451
|
get configs() {
|
|
473
|
-
return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep,
|
|
452
|
+
return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, testMode: this.testMode, tombstoneMode: this.tombstoneMode, sessionExpiry: this.sessionExpiryTimeoutMs, sweepTimeout: this.sweepTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
|
|
456
|
+
* before they are loaded or used. This is important to get accurate information of whether tombstoned object are
|
|
457
|
+
* in use or not.
|
|
458
|
+
*/
|
|
459
|
+
async initializeBaseState() {
|
|
460
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
461
|
+
/**
|
|
462
|
+
* The base snapshot data or tombstone state will not be present if the container is loaded from:
|
|
463
|
+
* 1. The first summary created by the detached container.
|
|
464
|
+
* 2. A summary that was generated with GC disabled.
|
|
465
|
+
* 3. A summary that was generated before GC even existed.
|
|
466
|
+
* 4. A summary that was generated with tombstone feature disabled.
|
|
467
|
+
*/
|
|
468
|
+
if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
this.tombstones = baseSnapshotData.tombstones;
|
|
472
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
474
473
|
}
|
|
475
474
|
/**
|
|
476
475
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -480,16 +479,20 @@ export class GarbageCollector {
|
|
|
480
479
|
*/
|
|
481
480
|
setConnectionState(connected, clientId) {
|
|
482
481
|
/**
|
|
483
|
-
* For
|
|
482
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
484
483
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
485
484
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
486
485
|
* could affect the GC state will have been processed.
|
|
487
486
|
*
|
|
487
|
+
* If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
|
|
488
|
+
* InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
|
|
489
|
+
* the receiving summarizer client.
|
|
490
|
+
*
|
|
488
491
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
489
492
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
490
493
|
*/
|
|
491
|
-
if (this.activeConnection() &&
|
|
492
|
-
this.
|
|
494
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
495
|
+
this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
|
|
493
496
|
}
|
|
494
497
|
}
|
|
495
498
|
/**
|
|
@@ -525,14 +528,14 @@ export class GarbageCollector {
|
|
|
525
528
|
const gcData = await this.runtime.getGCData(fullGC);
|
|
526
529
|
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
527
530
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
528
|
-
event.end(Object.assign({}, gcStats));
|
|
531
|
+
event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
|
|
529
532
|
this.completedRuns++;
|
|
530
533
|
return gcStats;
|
|
531
534
|
}, { end: true, cancel: "error" });
|
|
532
535
|
}
|
|
533
536
|
async runPreGCSteps() {
|
|
534
|
-
// Ensure that
|
|
535
|
-
await this.
|
|
537
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
538
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
536
539
|
// Let the runtime update its pending state before GC runs.
|
|
537
540
|
await this.runtime.updateStateBeforeGC();
|
|
538
541
|
}
|
|
@@ -545,14 +548,20 @@ export class GarbageCollector {
|
|
|
545
548
|
this.updateStateSinceLastRun(gcData, logger);
|
|
546
549
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
547
550
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
548
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
551
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
549
552
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
550
553
|
// delete these objects here instead.
|
|
551
554
|
this.logSweepEvents(logger, currentReferenceTimestampMs);
|
|
552
555
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
553
556
|
// involving access to deleted data.
|
|
554
557
|
if (this.testMode) {
|
|
555
|
-
this.runtime.
|
|
558
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
|
|
559
|
+
}
|
|
560
|
+
else if (this.tombstoneMode) {
|
|
561
|
+
// If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
|
|
562
|
+
// scenarios involving access to "deleted" data without actually deleting the data from summaries.
|
|
563
|
+
// Note: we will not tombstone in test mode
|
|
564
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
556
565
|
}
|
|
557
566
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
558
567
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
@@ -577,31 +586,69 @@ export class GarbageCollector {
|
|
|
577
586
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
578
587
|
};
|
|
579
588
|
}
|
|
580
|
-
const
|
|
589
|
+
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
590
|
+
const serializedTombstones = this.tombstoneMode
|
|
591
|
+
? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
|
|
592
|
+
: undefined;
|
|
581
593
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
594
|
+
* Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
|
|
595
|
+
* summary, send summary handles for them. Otherwise, send the data in summary blobs.
|
|
584
596
|
*/
|
|
585
597
|
if (this.trackGCState) {
|
|
586
|
-
this.
|
|
587
|
-
if (this.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
598
|
+
this.pendingSummaryData = { serializedGCState, serializedTombstones };
|
|
599
|
+
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
600
|
+
// If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
|
|
601
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
602
|
+
&& this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
603
|
+
const stats = mergeStats();
|
|
604
|
+
stats.handleNodeCount++;
|
|
605
|
+
return {
|
|
606
|
+
summary: {
|
|
607
|
+
type: SummaryType.Handle,
|
|
608
|
+
handle: `/${gcTreeKey}`,
|
|
609
|
+
handleType: SummaryType.Tree,
|
|
610
|
+
},
|
|
611
|
+
stats,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
// If either or both of GC state or tombstone state changed, build a GC summary tree.
|
|
615
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
601
616
|
}
|
|
602
617
|
}
|
|
618
|
+
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
619
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Builds the GC summary tree which contains GC state and tombstone state.
|
|
623
|
+
* If trackState is false, both GC state and tombstone state are written as summary blobs.
|
|
624
|
+
* If trackState is true, summary blob is written for GC state or tombstone state if they changed.
|
|
625
|
+
* @param serializedGCState - The GC state serialized as string.
|
|
626
|
+
* @param serializedTombstones - THe tombstone state serialized as string.
|
|
627
|
+
* @param trackState - Whether we are tracking GC state across summaries.
|
|
628
|
+
* @returns the GC summary tree.
|
|
629
|
+
*/
|
|
630
|
+
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
631
|
+
var _a, _b;
|
|
632
|
+
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
603
633
|
const builder = new SummaryTreeBuilder();
|
|
604
|
-
|
|
634
|
+
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
635
|
+
if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
|
|
636
|
+
builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
640
|
+
}
|
|
641
|
+
// If there is no tombstone data, return only the GC state.
|
|
642
|
+
if (serializedTombstones === undefined) {
|
|
643
|
+
return builder.getSummaryTree();
|
|
644
|
+
}
|
|
645
|
+
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
646
|
+
if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
|
|
647
|
+
builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
|
|
651
|
+
}
|
|
605
652
|
return builder.getSummaryTree();
|
|
606
653
|
}
|
|
607
654
|
getMetadata() {
|
|
@@ -613,6 +660,7 @@ export class GarbageCollector {
|
|
|
613
660
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
614
661
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
615
662
|
sweepEnabled: this.sweepEnabled,
|
|
663
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
616
664
|
};
|
|
617
665
|
}
|
|
618
666
|
/**
|
|
@@ -636,8 +684,8 @@ export class GarbageCollector {
|
|
|
636
684
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
637
685
|
this.initialStateNeedsReset = false;
|
|
638
686
|
if (this.trackGCState) {
|
|
639
|
-
this.
|
|
640
|
-
this.
|
|
687
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
688
|
+
this.pendingSummaryData = undefined;
|
|
641
689
|
}
|
|
642
690
|
return;
|
|
643
691
|
}
|
|
@@ -651,13 +699,16 @@ export class GarbageCollector {
|
|
|
651
699
|
}
|
|
652
700
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
653
701
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
654
|
-
const
|
|
655
|
-
this.
|
|
702
|
+
const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
703
|
+
this.latestSummaryData = {
|
|
704
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
705
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
706
|
+
};
|
|
656
707
|
}
|
|
657
708
|
else {
|
|
658
|
-
this.
|
|
709
|
+
this.latestSummaryData = undefined;
|
|
659
710
|
}
|
|
660
|
-
this.
|
|
711
|
+
this.pendingSummaryData = undefined;
|
|
661
712
|
}
|
|
662
713
|
/**
|
|
663
714
|
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
@@ -712,6 +763,7 @@ export class GarbageCollector {
|
|
|
712
763
|
*/
|
|
713
764
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
714
765
|
this.previousGCDataFromLastRun = cloneGCData(gcData);
|
|
766
|
+
this.tombstones = [];
|
|
715
767
|
this.newReferencesSinceLastRun.clear();
|
|
716
768
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
717
769
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
@@ -735,13 +787,24 @@ export class GarbageCollector {
|
|
|
735
787
|
}
|
|
736
788
|
else {
|
|
737
789
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
790
|
+
if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
|
|
791
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
792
|
+
if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
|
|
793
|
+
this.tombstones.push(nodeId);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
738
796
|
}
|
|
739
797
|
}
|
|
740
798
|
}
|
|
741
799
|
/**
|
|
742
800
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
743
|
-
* time.
|
|
744
|
-
*
|
|
801
|
+
* time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
|
|
802
|
+
* updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
|
|
803
|
+
* these objects while there can be in-memory referenced to it:
|
|
804
|
+
* 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
|
|
805
|
+
* added, the object may have been accessed and in-memory reference to it added.
|
|
806
|
+
* 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
|
|
807
|
+
* unreferenced, they could have been accessed and in-memory reference to them added.
|
|
745
808
|
*
|
|
746
809
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
747
810
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
@@ -753,7 +816,7 @@ export class GarbageCollector {
|
|
|
753
816
|
}
|
|
754
817
|
// Find any references that haven't been identified correctly.
|
|
755
818
|
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
|
|
756
|
-
if (
|
|
819
|
+
if (missingExplicitReferences.length > 0) {
|
|
757
820
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
758
821
|
const event = {
|
|
759
822
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -774,21 +837,18 @@ export class GarbageCollector {
|
|
|
774
837
|
* run, and then add the references since last run.
|
|
775
838
|
*
|
|
776
839
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
777
|
-
*
|
|
778
840
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
779
|
-
* references added new outbound references before
|
|
841
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
780
842
|
*
|
|
781
843
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
782
|
-
* references added new outbound references before
|
|
844
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
783
845
|
*
|
|
784
846
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
785
|
-
*
|
|
786
|
-
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
787
|
-
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
788
|
-
*
|
|
847
|
+
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
789
848
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
790
849
|
*/
|
|
791
850
|
const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
|
|
851
|
+
const newOutboundRoutesSinceLastRun = [];
|
|
792
852
|
this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
|
|
793
853
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
794
854
|
gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
|
|
@@ -796,19 +856,22 @@ export class GarbageCollector {
|
|
|
796
856
|
else {
|
|
797
857
|
gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
|
|
798
858
|
}
|
|
859
|
+
newOutboundRoutesSinceLastRun.push(...outboundRoutes);
|
|
799
860
|
});
|
|
800
861
|
/**
|
|
801
|
-
* Run GC on the above reference graph
|
|
862
|
+
* Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
|
|
863
|
+
* list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
|
|
802
864
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
803
|
-
*
|
|
865
|
+
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
866
|
+
* unreferenced and add unreferenced state.
|
|
804
867
|
*/
|
|
805
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
|
|
868
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
|
|
806
869
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
807
870
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
808
871
|
if (nodeStateTracker !== undefined) {
|
|
809
872
|
// Stop tracking so as to clear out any running timers.
|
|
810
873
|
nodeStateTracker.stopTracking();
|
|
811
|
-
// Delete the
|
|
874
|
+
// Delete the unreferenced state as we don't need to track it any more.
|
|
812
875
|
this.unreferencedNodesState.delete(nodeId);
|
|
813
876
|
}
|
|
814
877
|
}
|
|
@@ -964,31 +1027,24 @@ export class GarbageCollector {
|
|
|
964
1027
|
return;
|
|
965
1028
|
}
|
|
966
1029
|
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
967
|
-
const propsToLog = {
|
|
968
|
-
id: nodeId,
|
|
969
|
-
type: nodeType,
|
|
970
|
-
unrefTime: nodeStateTracker.unreferencedTimestampMs,
|
|
971
|
-
age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
|
|
972
|
-
timeout: nodeStateTracker.state === UnreferencedState.Inactive
|
|
1030
|
+
const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === UnreferencedState.Inactive
|
|
973
1031
|
? this.inactiveTimeoutMs
|
|
974
|
-
: this.sweepTimeoutMs,
|
|
975
|
-
completedGCRuns: this.completedRuns,
|
|
976
|
-
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
977
|
-
externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.externalRequest],
|
|
978
|
-
viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle],
|
|
979
|
-
fromId: fromNodeId,
|
|
980
|
-
};
|
|
1032
|
+
: this.sweepTimeoutMs, completedGCRuns: this.completedRuns, lastSummaryTime: this.getLastSummaryTimestampMs() }, this.createContainerMetadata), { externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.externalRequest], viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle], fromId: fromNodeId });
|
|
981
1033
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
982
1034
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
983
1035
|
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
1036
|
+
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
1037
|
+
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
984
1038
|
if (this.isSummarizerClient) {
|
|
985
1039
|
this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
|
|
986
1040
|
}
|
|
987
1041
|
else {
|
|
988
1042
|
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
989
1043
|
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
1044
|
+
// Events generated:
|
|
1045
|
+
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
990
1046
|
if (usageType === "Loaded") {
|
|
991
|
-
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePath
|
|
1047
|
+
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() }));
|
|
992
1048
|
}
|
|
993
1049
|
// If SweepReady Usage Detection is enabed, the handler may close the interactive container.
|
|
994
1050
|
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
@@ -999,6 +1055,11 @@ export class GarbageCollector {
|
|
|
999
1055
|
}
|
|
1000
1056
|
}
|
|
1001
1057
|
async logUnreferencedEvents(logger) {
|
|
1058
|
+
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
1059
|
+
// summary time they are then logged.
|
|
1060
|
+
// Events generated:
|
|
1061
|
+
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
1062
|
+
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
1002
1063
|
for (const eventProps of this.pendingEventsQueue) {
|
|
1003
1064
|
const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
|
|
1004
1065
|
/**
|
|
@@ -1019,12 +1080,17 @@ export class GarbageCollector {
|
|
|
1019
1080
|
}
|
|
1020
1081
|
}
|
|
1021
1082
|
/**
|
|
1022
|
-
* Gets the garbage collection state from the given snapshot tree.
|
|
1023
|
-
* Merge the GC state from all such blobs
|
|
1083
|
+
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1084
|
+
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1024
1085
|
*/
|
|
1025
|
-
async function
|
|
1086
|
+
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1026
1087
|
let rootGCState = { gcNodes: {} };
|
|
1088
|
+
let tombstones;
|
|
1027
1089
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1090
|
+
if (key === gcTombstoneBlobKey) {
|
|
1091
|
+
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1028
1094
|
// Skip blobs that do not start with the GC prefix.
|
|
1029
1095
|
if (!key.startsWith(gcBlobPrefix)) {
|
|
1030
1096
|
continue;
|
|
@@ -1038,7 +1104,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
|
1038
1104
|
// Merge the GC state of this blob into the root GC state.
|
|
1039
1105
|
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1040
1106
|
}
|
|
1041
|
-
return rootGCState;
|
|
1107
|
+
return { gcState: rootGCState, tombstones };
|
|
1042
1108
|
}
|
|
1043
1109
|
function generateSortedGCState(gcState) {
|
|
1044
1110
|
const sortableArray = Object.entries(gcState.gcNodes);
|