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