@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- package/dist/batchManager.d.ts +11 -6
- package/dist/batchManager.d.ts.map +1 -1
- package/dist/batchManager.js +23 -13
- package/dist/batchManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +74 -20
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +190 -137
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +6 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +14 -21
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +71 -57
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.js +1 -1
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStores.d.ts +11 -10
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +50 -20
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +36 -19
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +207 -121
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +3 -12
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/opCompressor.d.ts +18 -0
- package/dist/opCompressor.d.ts.map +1 -0
- package/dist/opCompressor.js +50 -0
- package/dist/opCompressor.js.map +1 -0
- package/dist/opDecompressor.d.ts +20 -0
- package/dist/opDecompressor.d.ts.map +1 -0
- package/dist/opDecompressor.js +72 -0
- package/dist/opDecompressor.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +6 -26
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +42 -62
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +3 -2
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +10 -3
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.js +7 -2
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.js +1 -1
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +0 -3
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +19 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts +4 -2
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +3 -2
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +10 -6
- package/dist/summaryManager.js.map +1 -1
- package/garbageCollection.md +27 -22
- package/lib/batchManager.d.ts +11 -6
- package/lib/batchManager.d.ts.map +1 -1
- package/lib/batchManager.js +23 -13
- package/lib/batchManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +74 -20
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +189 -136
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +6 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +14 -21
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +75 -61
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.js +1 -1
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStores.d.ts +11 -10
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +53 -23
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +36 -19
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +207 -121
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +3 -12
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -4
- package/lib/index.js.map +1 -1
- package/lib/opCompressor.d.ts +18 -0
- package/lib/opCompressor.d.ts.map +1 -0
- package/lib/opCompressor.js +46 -0
- package/lib/opCompressor.js.map +1 -0
- package/lib/opDecompressor.d.ts +20 -0
- package/lib/opDecompressor.d.ts.map +1 -0
- package/lib/opDecompressor.js +68 -0
- package/lib/opDecompressor.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +6 -26
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +42 -62
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +3 -2
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +10 -3
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.js +7 -2
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.js +1 -1
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +0 -3
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +19 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts +4 -2
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +3 -2
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +10 -6
- package/lib/summaryManager.js.map +1 -1
- package/package.json +37 -63
- package/prettier.config.cjs +8 -0
- package/src/batchManager.ts +32 -15
- package/src/containerRuntime.ts +260 -156
- package/src/dataStore.ts +13 -1
- package/src/dataStoreContext.ts +100 -76
- package/src/dataStoreContexts.ts +1 -1
- package/src/dataStores.ts +61 -23
- package/src/garbageCollection.ts +255 -126
- package/src/gcSweepReadyUsageDetection.ts +2 -10
- package/src/index.ts +4 -4
- package/src/opCompressor.ts +59 -0
- package/src/opDecompressor.ts +82 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +57 -96
- package/src/runningSummarizer.ts +11 -3
- package/src/scheduleManager.ts +1 -0
- package/src/summarizer.ts +6 -6
- package/src/summarizerClientElection.ts +1 -1
- package/src/summarizerHeuristics.ts +0 -3
- package/src/summarizerTypes.ts +20 -7
- package/src/summaryFormat.ts +4 -2
- package/src/summaryGenerator.ts +3 -2
- package/src/summaryManager.ts +18 -7
|
@@ -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 = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.
|
|
18
|
+
exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.throwOnTombstoneUsageKey = exports.disableTombstoneKey = exports.disableSweepLogKey = exports.trackGCStateKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcTombstoneBlobKey = exports.gcBlobPrefix = exports.gcTreeKey = 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");
|
|
@@ -33,22 +33,24 @@ const GCVersion = 1;
|
|
|
33
33
|
exports.gcTreeKey = "gc";
|
|
34
34
|
// They prefix for GC blobs in the GC tree in summary.
|
|
35
35
|
exports.gcBlobPrefix = "__gc";
|
|
36
|
+
// The key for tombstone blob in the GC tree in summary.
|
|
37
|
+
exports.gcTombstoneBlobKey = "__tombstones";
|
|
36
38
|
// Feature gate key to turn GC on / off.
|
|
37
39
|
exports.runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
38
40
|
// Feature gate key to turn GC sweep on / off.
|
|
39
41
|
exports.runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
40
42
|
// Feature gate key to turn GC test mode on / off.
|
|
41
43
|
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
44
|
// Feature gate key to expire a session after a set period of time.
|
|
45
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
46
|
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
49
47
|
exports.trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
50
48
|
// Feature gate key to turn GC sweep log off.
|
|
51
49
|
exports.disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
50
|
+
// Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
|
|
51
|
+
exports.disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
|
|
52
|
+
// Feature gate to enable throwing an error when tombstone object is used.
|
|
53
|
+
exports.throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
|
|
52
54
|
// One day in milliseconds.
|
|
53
55
|
exports.oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
54
56
|
exports.defaultInactiveTimeoutMs = 7 * exports.oneDayMs; // 7 days
|
|
@@ -166,10 +168,6 @@ exports.UnreferencedStateTracker = UnreferencedStateTracker;
|
|
|
166
168
|
class GarbageCollector {
|
|
167
169
|
constructor(createParams) {
|
|
168
170
|
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
171
|
/**
|
|
174
172
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
175
173
|
*
|
|
@@ -188,6 +186,7 @@ class GarbageCollector {
|
|
|
188
186
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
189
187
|
// outbound routes from that node.
|
|
190
188
|
this.newReferencesSinceLastRun = new Map();
|
|
189
|
+
this.tombstones = [];
|
|
191
190
|
// Map of node ids to their unreferenced state tracker.
|
|
192
191
|
this.unreferencedNodesState = new Map();
|
|
193
192
|
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
@@ -200,6 +199,7 @@ class GarbageCollector {
|
|
|
200
199
|
this.runtime = createParams.runtime;
|
|
201
200
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
202
201
|
this.gcOptions = createParams.gcOptions;
|
|
202
|
+
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
203
203
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
204
204
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
205
205
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -209,6 +209,20 @@ class GarbageCollector {
|
|
|
209
209
|
this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
|
|
210
210
|
this.sweepReadyUsageHandler = new gcSweepReadyUsageDetection_1.SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
211
211
|
let prevSummaryGCVersion;
|
|
212
|
+
/**
|
|
213
|
+
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
214
|
+
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
|
|
215
|
+
*
|
|
216
|
+
* The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
|
|
217
|
+
* The buffer is added to account for any clock skew or other edge cases.
|
|
218
|
+
* We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
|
|
219
|
+
*/
|
|
220
|
+
function computeSweepTimeout(sessionExpiryTimeoutMs) {
|
|
221
|
+
const maxSnapshotCacheExpiryMs = 5 * exports.oneDayMs;
|
|
222
|
+
const bufferMs = exports.oneDayMs;
|
|
223
|
+
return sessionExpiryTimeoutMs &&
|
|
224
|
+
(sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
225
|
+
}
|
|
212
226
|
/**
|
|
213
227
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
214
228
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -223,6 +237,8 @@ class GarbageCollector {
|
|
|
223
237
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
224
238
|
this.sweepEnabled = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.sweepEnabled) !== null && _a !== void 0 ? _a : false;
|
|
225
239
|
this.sessionExpiryTimeoutMs = metadata === null || metadata === void 0 ? void 0 : metadata.sessionExpiryTimeoutMs;
|
|
240
|
+
this.sweepTimeoutMs =
|
|
241
|
+
(_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
242
|
}
|
|
227
243
|
else {
|
|
228
244
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
@@ -230,36 +246,28 @@ class GarbageCollector {
|
|
|
230
246
|
if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
|
|
231
247
|
throw new container_utils_1.UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
232
248
|
}
|
|
249
|
+
// This Test Override only applies for new containers
|
|
250
|
+
const testOverrideSweepTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
233
251
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
234
252
|
// flag in GC options to false.
|
|
235
253
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
236
254
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
237
|
-
|
|
255
|
+
// ...unless we're using the TestOverride
|
|
256
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
238
257
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
239
258
|
if (this.mc.config.getBoolean(exports.runSessionExpiryKey) && this.gcEnabled) {
|
|
240
|
-
this.sessionExpiryTimeoutMs = (
|
|
259
|
+
this.sessionExpiryTimeoutMs = (_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : exports.defaultSessionExpiryDurationMs;
|
|
241
260
|
}
|
|
261
|
+
this.sweepTimeoutMs =
|
|
262
|
+
testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
242
263
|
}
|
|
243
264
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
244
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
265
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
245
266
|
// If Test Override config is set, override Session Expiry timeout.
|
|
246
267
|
const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
247
268
|
const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
|
|
248
269
|
this.sessionExpiryTimer = new common_utils_1.Timer(timeoutMs, () => { this.runtime.closeFn(new container_utils_1.ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
|
|
249
270
|
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
271
|
}
|
|
264
272
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
265
273
|
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
@@ -282,36 +290,34 @@ class GarbageCollector {
|
|
|
282
290
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
283
291
|
*
|
|
284
292
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
285
|
-
*
|
|
286
293
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
287
|
-
*
|
|
288
|
-
*
|
|
294
|
+
* 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
|
|
295
|
+
* the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
|
|
296
|
+
* 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
289
297
|
* feature flag.
|
|
290
298
|
*/
|
|
291
|
-
this.shouldRunSweep =
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
299
|
+
this.shouldRunSweep =
|
|
300
|
+
this.shouldRunGC
|
|
301
|
+
&& this.sweepTimeoutMs !== undefined
|
|
302
|
+
&& ((_e = this.mc.config.getBoolean(exports.runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
|
|
295
303
|
this.trackGCState = this.mc.config.getBoolean(exports.trackGCStateKey) === true;
|
|
296
304
|
// Override inactive timeout if test config or gc options to override it is set.
|
|
297
|
-
this.inactiveTimeoutMs = (
|
|
305
|
+
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 : exports.defaultInactiveTimeoutMs;
|
|
298
306
|
// Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
|
|
299
307
|
if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
|
|
300
308
|
throw new container_utils_1.UsageError("inactive timeout should not be greater than the sweep timeout");
|
|
301
309
|
}
|
|
302
310
|
// 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 () => {
|
|
311
|
+
this.testMode = (_h = this.mc.config.getBoolean(exports.gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
|
|
312
|
+
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
313
|
+
this.tombstoneMode = this.mc.config.getBoolean(exports.disableTombstoneKey) !== true;
|
|
314
|
+
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
315
|
+
// contain GC tree and GC is enabled.
|
|
316
|
+
const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[exports.gcTreeKey]) !== undefined;
|
|
317
|
+
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
318
|
+
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
319
|
+
// it involves fetching blobs from storage which is expensive.
|
|
320
|
+
this.baseSnapshotDataP = new common_utils_1.LazyPromise(async () => {
|
|
315
321
|
var _a;
|
|
316
322
|
if (baseSnapshot === undefined) {
|
|
317
323
|
return undefined;
|
|
@@ -320,13 +326,7 @@ class GarbageCollector {
|
|
|
320
326
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
321
327
|
const gcSnapshotTree = baseSnapshot.trees[exports.gcTreeKey];
|
|
322
328
|
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;
|
|
329
|
+
return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
330
330
|
}
|
|
331
331
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
332
332
|
// consolidate into IGarbageCollectionState format.
|
|
@@ -362,7 +362,7 @@ class GarbageCollector {
|
|
|
362
362
|
}
|
|
363
363
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
364
364
|
// 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;
|
|
365
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
|
|
366
366
|
}
|
|
367
367
|
catch (error) {
|
|
368
368
|
const dpe = container_utils_1.DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
@@ -371,11 +371,11 @@ class GarbageCollector {
|
|
|
371
371
|
}
|
|
372
372
|
});
|
|
373
373
|
/**
|
|
374
|
-
* Set up the initializer which initializes the
|
|
375
|
-
*
|
|
376
|
-
*
|
|
374
|
+
* Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
|
|
375
|
+
* connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
|
|
376
|
+
* GC state and updates their inactive or sweep ready state.
|
|
377
377
|
*/
|
|
378
|
-
this.
|
|
378
|
+
this.initializeGCStateFromBaseSnapshotP = new common_utils_1.LazyPromise(async () => {
|
|
379
379
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
380
380
|
/**
|
|
381
381
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
@@ -393,34 +393,41 @@ class GarbageCollector {
|
|
|
393
393
|
});
|
|
394
394
|
return;
|
|
395
395
|
}
|
|
396
|
-
const
|
|
396
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
397
397
|
/**
|
|
398
|
-
* The base
|
|
398
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
399
399
|
* 1. The first summary created by the detached container.
|
|
400
400
|
* 2. A summary that was generated with GC disabled.
|
|
401
401
|
* 3. A summary that was generated before GC even existed.
|
|
402
402
|
*/
|
|
403
|
-
if (
|
|
403
|
+
if (baseSnapshotData === undefined) {
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
406
|
const gcNodes = {};
|
|
407
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
407
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
408
408
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
409
409
|
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
410
410
|
}
|
|
411
411
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
412
412
|
}
|
|
413
413
|
this.previousGCDataFromLastRun = { gcNodes };
|
|
414
|
+
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
415
|
+
if (this.trackGCState) {
|
|
416
|
+
this.latestSummaryData = {
|
|
417
|
+
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
418
|
+
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
414
421
|
});
|
|
415
422
|
// Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
|
|
416
423
|
// which the caller uses to initialize each node's GC state.
|
|
417
424
|
this.baseGCDetailsP = new common_utils_1.LazyPromise(async () => {
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
425
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
426
|
+
if (baseSnapshotData === undefined) {
|
|
420
427
|
return new Map();
|
|
421
428
|
}
|
|
422
429
|
const gcNodes = {};
|
|
423
|
-
for (const [nodeId, nodeData] of Object.entries(
|
|
430
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
424
431
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
425
432
|
}
|
|
426
433
|
// Run GC on the nodes in the base summary to get the routes used in each node in the container.
|
|
@@ -430,7 +437,7 @@ class GarbageCollector {
|
|
|
430
437
|
const baseGCDetailsMap = (0, garbage_collector_1.unpackChildNodesGCDetails)({ gcData: { gcNodes }, usedRoutes });
|
|
431
438
|
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
432
439
|
// 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(
|
|
440
|
+
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
434
441
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
435
442
|
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
436
443
|
if (dataStoreGCDetails !== undefined) {
|
|
@@ -469,12 +476,29 @@ class GarbageCollector {
|
|
|
469
476
|
return this.initialStateNeedsReset ||
|
|
470
477
|
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
|
|
471
478
|
}
|
|
472
|
-
get writeDataAtRoot() {
|
|
473
|
-
return this._writeDataAtRoot;
|
|
474
|
-
}
|
|
475
479
|
/** Returns a list of all the configurations for garbage collection. */
|
|
476
480
|
get configs() {
|
|
477
|
-
return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep,
|
|
481
|
+
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);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
|
|
485
|
+
* before they are loaded or used. This is important to get accurate information of whether tombstoned object are
|
|
486
|
+
* in use or not.
|
|
487
|
+
*/
|
|
488
|
+
async initializeBaseState() {
|
|
489
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
490
|
+
/**
|
|
491
|
+
* The base snapshot data or tombstone state will not be present if the container is loaded from:
|
|
492
|
+
* 1. The first summary created by the detached container.
|
|
493
|
+
* 2. A summary that was generated with GC disabled.
|
|
494
|
+
* 3. A summary that was generated before GC even existed.
|
|
495
|
+
* 4. A summary that was generated with tombstone feature disabled.
|
|
496
|
+
*/
|
|
497
|
+
if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
this.tombstones = baseSnapshotData.tombstones;
|
|
501
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
478
502
|
}
|
|
479
503
|
/**
|
|
480
504
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -484,16 +508,20 @@ class GarbageCollector {
|
|
|
484
508
|
*/
|
|
485
509
|
setConnectionState(connected, clientId) {
|
|
486
510
|
/**
|
|
487
|
-
* For
|
|
511
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
488
512
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
489
513
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
490
514
|
* could affect the GC state will have been processed.
|
|
491
515
|
*
|
|
516
|
+
* If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
|
|
517
|
+
* InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
|
|
518
|
+
* the receiving summarizer client.
|
|
519
|
+
*
|
|
492
520
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
493
521
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
494
522
|
*/
|
|
495
|
-
if (this.activeConnection() &&
|
|
496
|
-
this.
|
|
523
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
524
|
+
this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
|
|
497
525
|
}
|
|
498
526
|
}
|
|
499
527
|
/**
|
|
@@ -529,14 +557,14 @@ class GarbageCollector {
|
|
|
529
557
|
const gcData = await this.runtime.getGCData(fullGC);
|
|
530
558
|
const gcResult = (0, garbage_collector_1.runGarbageCollection)(gcData.gcNodes, ["/"]);
|
|
531
559
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
532
|
-
event.end(Object.assign({}, gcStats));
|
|
560
|
+
event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
|
|
533
561
|
this.completedRuns++;
|
|
534
562
|
return gcStats;
|
|
535
563
|
}, { end: true, cancel: "error" });
|
|
536
564
|
}
|
|
537
565
|
async runPreGCSteps() {
|
|
538
|
-
// Ensure that
|
|
539
|
-
await this.
|
|
566
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
567
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
540
568
|
// Let the runtime update its pending state before GC runs.
|
|
541
569
|
await this.runtime.updateStateBeforeGC();
|
|
542
570
|
}
|
|
@@ -549,14 +577,20 @@ class GarbageCollector {
|
|
|
549
577
|
this.updateStateSinceLastRun(gcData, logger);
|
|
550
578
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
551
579
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
552
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
580
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
553
581
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
554
582
|
// delete these objects here instead.
|
|
555
583
|
this.logSweepEvents(logger, currentReferenceTimestampMs);
|
|
556
584
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
557
585
|
// involving access to deleted data.
|
|
558
586
|
if (this.testMode) {
|
|
559
|
-
this.runtime.
|
|
587
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
|
|
588
|
+
}
|
|
589
|
+
else if (this.tombstoneMode) {
|
|
590
|
+
// If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
|
|
591
|
+
// scenarios involving access to "deleted" data without actually deleting the data from summaries.
|
|
592
|
+
// Note: we will not tombstone in test mode
|
|
593
|
+
this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
|
|
560
594
|
}
|
|
561
595
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
562
596
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
@@ -581,31 +615,69 @@ class GarbageCollector {
|
|
|
581
615
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
582
616
|
};
|
|
583
617
|
}
|
|
584
|
-
const
|
|
618
|
+
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
619
|
+
const serializedTombstones = this.tombstoneMode
|
|
620
|
+
? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
|
|
621
|
+
: undefined;
|
|
585
622
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
623
|
+
* Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
|
|
624
|
+
* summary, send summary handles for them. Otherwise, send the data in summary blobs.
|
|
588
625
|
*/
|
|
589
626
|
if (this.trackGCState) {
|
|
590
|
-
this.
|
|
591
|
-
if (this.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
627
|
+
this.pendingSummaryData = { serializedGCState, serializedTombstones };
|
|
628
|
+
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
629
|
+
// If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
|
|
630
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
631
|
+
&& this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
632
|
+
const stats = (0, runtime_utils_1.mergeStats)();
|
|
633
|
+
stats.handleNodeCount++;
|
|
634
|
+
return {
|
|
635
|
+
summary: {
|
|
636
|
+
type: protocol_definitions_1.SummaryType.Handle,
|
|
637
|
+
handle: `/${exports.gcTreeKey}`,
|
|
638
|
+
handleType: protocol_definitions_1.SummaryType.Tree,
|
|
639
|
+
},
|
|
640
|
+
stats,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
// If either or both of GC state or tombstone state changed, build a GC summary tree.
|
|
644
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
605
645
|
}
|
|
606
646
|
}
|
|
647
|
+
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
648
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Builds the GC summary tree which contains GC state and tombstone state.
|
|
652
|
+
* If trackState is false, both GC state and tombstone state are written as summary blobs.
|
|
653
|
+
* If trackState is true, summary blob is written for GC state or tombstone state if they changed.
|
|
654
|
+
* @param serializedGCState - The GC state serialized as string.
|
|
655
|
+
* @param serializedTombstones - THe tombstone state serialized as string.
|
|
656
|
+
* @param trackState - Whether we are tracking GC state across summaries.
|
|
657
|
+
* @returns the GC summary tree.
|
|
658
|
+
*/
|
|
659
|
+
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
660
|
+
var _a, _b;
|
|
661
|
+
const gcStateBlobKey = `${exports.gcBlobPrefix}_root`;
|
|
607
662
|
const builder = new runtime_utils_1.SummaryTreeBuilder();
|
|
608
|
-
|
|
663
|
+
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
664
|
+
if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
|
|
665
|
+
builder.addHandle(gcStateBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${gcStateBlobKey}`);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
669
|
+
}
|
|
670
|
+
// If there is no tombstone data, return only the GC state.
|
|
671
|
+
if (serializedTombstones === undefined) {
|
|
672
|
+
return builder.getSummaryTree();
|
|
673
|
+
}
|
|
674
|
+
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
675
|
+
if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
|
|
676
|
+
builder.addHandle(exports.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${exports.gcTreeKey}/${exports.gcTombstoneBlobKey}`);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
builder.addBlob(exports.gcTombstoneBlobKey, serializedTombstones);
|
|
680
|
+
}
|
|
609
681
|
return builder.getSummaryTree();
|
|
610
682
|
}
|
|
611
683
|
getMetadata() {
|
|
@@ -617,6 +689,7 @@ class GarbageCollector {
|
|
|
617
689
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
618
690
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
619
691
|
sweepEnabled: this.sweepEnabled,
|
|
692
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
620
693
|
};
|
|
621
694
|
}
|
|
622
695
|
/**
|
|
@@ -640,8 +713,8 @@ class GarbageCollector {
|
|
|
640
713
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
641
714
|
this.initialStateNeedsReset = false;
|
|
642
715
|
if (this.trackGCState) {
|
|
643
|
-
this.
|
|
644
|
-
this.
|
|
716
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
717
|
+
this.pendingSummaryData = undefined;
|
|
645
718
|
}
|
|
646
719
|
return;
|
|
647
720
|
}
|
|
@@ -655,13 +728,16 @@ class GarbageCollector {
|
|
|
655
728
|
}
|
|
656
729
|
const gcSnapshotTree = snapshot.trees[exports.gcTreeKey];
|
|
657
730
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
658
|
-
const
|
|
659
|
-
this.
|
|
731
|
+
const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
732
|
+
this.latestSummaryData = {
|
|
733
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
734
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
735
|
+
};
|
|
660
736
|
}
|
|
661
737
|
else {
|
|
662
|
-
this.
|
|
738
|
+
this.latestSummaryData = undefined;
|
|
663
739
|
}
|
|
664
|
-
this.
|
|
740
|
+
this.pendingSummaryData = undefined;
|
|
665
741
|
}
|
|
666
742
|
/**
|
|
667
743
|
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
@@ -716,6 +792,7 @@ class GarbageCollector {
|
|
|
716
792
|
*/
|
|
717
793
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
718
794
|
this.previousGCDataFromLastRun = (0, garbage_collector_1.cloneGCData)(gcData);
|
|
795
|
+
this.tombstones = [];
|
|
719
796
|
this.newReferencesSinceLastRun.clear();
|
|
720
797
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
721
798
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
@@ -739,6 +816,12 @@ class GarbageCollector {
|
|
|
739
816
|
}
|
|
740
817
|
else {
|
|
741
818
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
819
|
+
if (this.tombstoneMode && nodeStateTracker.state === exports.UnreferencedState.SweepReady) {
|
|
820
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
821
|
+
if (nodeType === exports.GCNodeType.DataStore || nodeType === exports.GCNodeType.Blob) {
|
|
822
|
+
this.tombstones.push(nodeId);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
742
825
|
}
|
|
743
826
|
}
|
|
744
827
|
}
|
|
@@ -757,7 +840,7 @@ class GarbageCollector {
|
|
|
757
840
|
}
|
|
758
841
|
// Find any references that haven't been identified correctly.
|
|
759
842
|
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
|
|
760
|
-
if (
|
|
843
|
+
if (missingExplicitReferences.length > 0) {
|
|
761
844
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
762
845
|
const event = {
|
|
763
846
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -968,31 +1051,24 @@ class GarbageCollector {
|
|
|
968
1051
|
return;
|
|
969
1052
|
}
|
|
970
1053
|
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
|
|
1054
|
+
const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === exports.UnreferencedState.Inactive
|
|
977
1055
|
? 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
|
-
};
|
|
1056
|
+
: 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
1057
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
986
1058
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
987
1059
|
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
1060
|
+
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
1061
|
+
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
988
1062
|
if (this.isSummarizerClient) {
|
|
989
1063
|
this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
|
|
990
1064
|
}
|
|
991
1065
|
else {
|
|
992
1066
|
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
993
1067
|
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
1068
|
+
// Events generated:
|
|
1069
|
+
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
994
1070
|
if (usageType === "Loaded") {
|
|
995
|
-
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg:
|
|
1071
|
+
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
1072
|
}
|
|
997
1073
|
// If SweepReady Usage Detection is enabed, the handler may close the interactive container.
|
|
998
1074
|
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
@@ -1003,6 +1079,11 @@ class GarbageCollector {
|
|
|
1003
1079
|
}
|
|
1004
1080
|
}
|
|
1005
1081
|
async logUnreferencedEvents(logger) {
|
|
1082
|
+
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
1083
|
+
// summary time they are then logged.
|
|
1084
|
+
// Events generated:
|
|
1085
|
+
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
1086
|
+
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
1006
1087
|
for (const eventProps of this.pendingEventsQueue) {
|
|
1007
1088
|
const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
|
|
1008
1089
|
/**
|
|
@@ -1024,12 +1105,17 @@ class GarbageCollector {
|
|
|
1024
1105
|
}
|
|
1025
1106
|
exports.GarbageCollector = GarbageCollector;
|
|
1026
1107
|
/**
|
|
1027
|
-
* Gets the garbage collection state from the given snapshot tree.
|
|
1028
|
-
* Merge the GC state from all such blobs
|
|
1108
|
+
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1109
|
+
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1029
1110
|
*/
|
|
1030
|
-
async function
|
|
1111
|
+
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1031
1112
|
let rootGCState = { gcNodes: {} };
|
|
1113
|
+
let tombstones;
|
|
1032
1114
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1115
|
+
if (key === exports.gcTombstoneBlobKey) {
|
|
1116
|
+
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1033
1119
|
// Skip blobs that do not start with the GC prefix.
|
|
1034
1120
|
if (!key.startsWith(exports.gcBlobPrefix)) {
|
|
1035
1121
|
continue;
|
|
@@ -1043,7 +1129,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
|
1043
1129
|
// Merge the GC state of this blob into the root GC state.
|
|
1044
1130
|
rootGCState = (0, garbage_collector_1.concatGarbageCollectionStates)(rootGCState, gcState);
|
|
1045
1131
|
}
|
|
1046
|
-
return rootGCState;
|
|
1132
|
+
return { gcState: rootGCState, tombstones };
|
|
1047
1133
|
}
|
|
1048
1134
|
function generateSortedGCState(gcState) {
|
|
1049
1135
|
const sortableArray = Object.entries(gcState.gcNodes);
|