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