@fluidframework/container-runtime 2.0.0-internal.2.2.0 → 2.0.0-internal.2.3.0
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 +19 -8
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +44 -33
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +130 -97
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +39 -8
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +117 -61
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +4 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +9 -6
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +30 -24
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -20
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +205 -151
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +6 -3
- package/dist/garbageCollectionConstants.d.ts.map +1 -1
- package/dist/garbageCollectionConstants.js +7 -7
- package/dist/garbageCollectionConstants.js.map +1 -1
- package/dist/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/dist/garbageCollectionTombstoneUtils.js +28 -0
- package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +13 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +35 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +25 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +24 -10
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +30 -17
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts +34 -2
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +114 -5
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +5 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +24 -14
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +0 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +0 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +9 -20
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.d.ts +0 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +2 -1
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +1 -0
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +1 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +44 -33
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +131 -98
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +39 -8
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +115 -59
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +5 -4
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +9 -6
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +32 -26
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -20
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +201 -147
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +6 -3
- package/lib/garbageCollectionConstants.d.ts.map +1 -1
- package/lib/garbageCollectionConstants.js +6 -6
- package/lib/garbageCollectionConstants.js.map +1 -1
- package/lib/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/lib/garbageCollectionTombstoneUtils.js +24 -0
- package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +13 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +25 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +24 -10
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +30 -17
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts +34 -2
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +112 -4
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +5 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +24 -14
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +0 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +0 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +9 -20
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.d.ts +0 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +2 -1
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +1 -0
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +1 -2
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +31 -27
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +146 -103
- package/src/containerRuntime.ts +166 -65
- package/src/dataStoreContext.ts +5 -5
- package/src/dataStores.ts +40 -30
- package/src/garbageCollection.ts +254 -183
- package/src/garbageCollectionConstants.ts +7 -6
- package/src/garbageCollectionTombstoneUtils.ts +31 -0
- package/src/index.ts +0 -5
- package/src/opLifecycle/batchManager.ts +59 -1
- package/src/opLifecycle/definitions.ts +27 -1
- package/src/opLifecycle/index.ts +2 -1
- package/src/opLifecycle/opCompressor.ts +29 -12
- package/src/opLifecycle/opDecompressor.ts +39 -18
- package/src/opLifecycle/opSplitter.ts +141 -7
- package/src/opLifecycle/outbox.ts +32 -16
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +0 -1
- package/src/scheduleManager.ts +19 -30
- package/src/summarizer.ts +1 -1
- package/src/summarizerTypes.ts +1 -0
- package/src/summaryFormat.ts +1 -2
|
@@ -28,8 +28,6 @@ const dataStores_1 = require("./dataStores");
|
|
|
28
28
|
const garbageCollectionConstants_1 = require("./garbageCollectionConstants");
|
|
29
29
|
const gcSweepReadyUsageDetection_1 = require("./gcSweepReadyUsageDetection");
|
|
30
30
|
const summaryFormat_1 = require("./summaryFormat");
|
|
31
|
-
/** This is the current version of garbage collection. */
|
|
32
|
-
const GCVersion = 1;
|
|
33
31
|
/** The types of GC nodes in the GC reference graph. */
|
|
34
32
|
exports.GCNodeType = {
|
|
35
33
|
// Nodes that are for data stores.
|
|
@@ -143,21 +141,6 @@ exports.UnreferencedStateTracker = UnreferencedStateTracker;
|
|
|
143
141
|
class GarbageCollector {
|
|
144
142
|
constructor(createParams) {
|
|
145
143
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
146
|
-
/**
|
|
147
|
-
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
148
|
-
*
|
|
149
|
-
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
150
|
-
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
151
|
-
*
|
|
152
|
-
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
153
|
-
* a document and the first time GC is enabled after is was disabled before.
|
|
154
|
-
*
|
|
155
|
-
* Note that the state needs reset only for the very first time summary is generated by this client. After that, the
|
|
156
|
-
* state will be up-to-date and this flag will be reset.
|
|
157
|
-
*/
|
|
158
|
-
this.initialStateNeedsReset = false;
|
|
159
|
-
// The current GC version that this container is running.
|
|
160
|
-
this.currentGCVersion = GCVersion;
|
|
161
144
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
162
145
|
// outbound routes from that node.
|
|
163
146
|
this.newReferencesSinceLastRun = new Map();
|
|
@@ -182,6 +165,9 @@ class GarbageCollector {
|
|
|
182
165
|
const metadata = createParams.metadata;
|
|
183
166
|
const readAndParseBlob = createParams.readAndParseBlob;
|
|
184
167
|
this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
|
|
168
|
+
// If version upgrade is not enabled, fall back to the stable GC version.
|
|
169
|
+
this.currentGCVersion =
|
|
170
|
+
this.mc.config.getBoolean(garbageCollectionConstants_1.gcVersionUpgradeToV2Key) === true ? garbageCollectionConstants_1.currentGCVersion : garbageCollectionConstants_1.stableGCVersion;
|
|
185
171
|
this.sweepReadyUsageHandler = new gcSweepReadyUsageDetection_1.SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
186
172
|
let prevSummaryGCVersion;
|
|
187
173
|
/**
|
|
@@ -286,10 +272,8 @@ class GarbageCollector {
|
|
|
286
272
|
this.testMode = (_h = this.mc.config.getBoolean(garbageCollectionConstants_1.gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
|
|
287
273
|
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
288
274
|
this.tombstoneMode = this.mc.config.getBoolean(garbageCollectionConstants_1.disableTombstoneKey) !== true;
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[garbageCollectionConstants_1.gcTreeKey]) !== undefined;
|
|
292
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
275
|
+
// If GC ran in the container that generated the base snapshot, it will have a GC tree.
|
|
276
|
+
this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[runtime_definitions_1.gcTreeKey]) !== undefined;
|
|
293
277
|
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
294
278
|
// it involves fetching blobs from storage which is expensive.
|
|
295
279
|
this.baseSnapshotDataP = new common_utils_1.LazyPromise(async () => {
|
|
@@ -299,9 +283,9 @@ class GarbageCollector {
|
|
|
299
283
|
}
|
|
300
284
|
try {
|
|
301
285
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
302
|
-
const gcSnapshotTree = baseSnapshot.trees[
|
|
286
|
+
const gcSnapshotTree = baseSnapshot.trees[runtime_definitions_1.gcTreeKey];
|
|
303
287
|
if (gcSnapshotTree !== undefined) {
|
|
304
|
-
return getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
288
|
+
return (0, garbage_collector_1.getGCDataFromSnapshot)(gcSnapshotTree, readAndParseBlob);
|
|
305
289
|
}
|
|
306
290
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
307
291
|
// consolidate into IGarbageCollectionState format.
|
|
@@ -310,7 +294,7 @@ class GarbageCollector {
|
|
|
310
294
|
const dataStoreSnapshotTree = (0, dataStores_1.getSummaryForDatastores)(baseSnapshot, metadata);
|
|
311
295
|
(0, common_utils_1.assert)(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
312
296
|
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
313
|
-
const blobId = dsSnapshotTree.blobs[runtime_definitions_1.
|
|
297
|
+
const blobId = dsSnapshotTree.blobs[runtime_definitions_1.gcTreeKey];
|
|
314
298
|
if (blobId === undefined) {
|
|
315
299
|
continue;
|
|
316
300
|
}
|
|
@@ -351,7 +335,6 @@ class GarbageCollector {
|
|
|
351
335
|
* GC state and updates their inactive or sweep ready state.
|
|
352
336
|
*/
|
|
353
337
|
this.initializeGCStateFromBaseSnapshotP = new common_utils_1.LazyPromise(async () => {
|
|
354
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
355
338
|
/**
|
|
356
339
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
357
340
|
* how long objects have been unreferenced and if they can be deleted.
|
|
@@ -360,6 +343,7 @@ class GarbageCollector {
|
|
|
360
343
|
* for this container and it is in read mode. In this scenario, there is no point in running GC anyway
|
|
361
344
|
* because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
362
345
|
*/
|
|
346
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
363
347
|
if (currentReferenceTimestampMs === undefined) {
|
|
364
348
|
// Log an event so we can evaluate how often we run into this scenario.
|
|
365
349
|
this.mc.logger.sendErrorEvent({
|
|
@@ -368,38 +352,24 @@ class GarbageCollector {
|
|
|
368
352
|
});
|
|
369
353
|
return;
|
|
370
354
|
}
|
|
371
|
-
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
372
355
|
/**
|
|
373
356
|
* The base snapshot data will not be present if the container is loaded from:
|
|
374
357
|
* 1. The first summary created by the detached container.
|
|
375
358
|
* 2. A summary that was generated with GC disabled.
|
|
376
359
|
* 3. A summary that was generated before GC even existed.
|
|
377
360
|
*/
|
|
361
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
378
362
|
if (baseSnapshotData === undefined) {
|
|
379
363
|
return;
|
|
380
364
|
}
|
|
381
|
-
|
|
382
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
383
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
384
|
-
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
385
|
-
}
|
|
386
|
-
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
387
|
-
}
|
|
388
|
-
this.previousGCDataFromLastRun = { gcNodes };
|
|
389
|
-
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
390
|
-
if (this.trackGCState) {
|
|
391
|
-
this.latestSummaryData = {
|
|
392
|
-
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
393
|
-
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
394
|
-
};
|
|
395
|
-
}
|
|
365
|
+
this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
|
|
396
366
|
});
|
|
397
|
-
// Get the GC details
|
|
398
|
-
//
|
|
367
|
+
// Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
|
|
368
|
+
// used to initialize the GC state of all the nodes in the container.
|
|
399
369
|
this.baseGCDetailsP = new common_utils_1.LazyPromise(async () => {
|
|
400
370
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
401
371
|
if (baseSnapshotData === undefined) {
|
|
402
|
-
return
|
|
372
|
+
return {};
|
|
403
373
|
}
|
|
404
374
|
const gcNodes = {};
|
|
405
375
|
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
@@ -409,18 +379,7 @@ class GarbageCollector {
|
|
|
409
379
|
// This is an optimization for space (vs performance) wherein we don't need to store the used routes of
|
|
410
380
|
// each node in the summary.
|
|
411
381
|
const usedRoutes = (0, garbage_collector_1.runGarbageCollection)(gcNodes, ["/"]).referencedNodeIds;
|
|
412
|
-
|
|
413
|
-
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
414
|
-
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
415
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
416
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
417
|
-
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
418
|
-
if (dataStoreGCDetails !== undefined) {
|
|
419
|
-
dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return baseGCDetailsMap;
|
|
382
|
+
return { gcData: { gcNodes }, usedRoutes };
|
|
424
383
|
});
|
|
425
384
|
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
426
385
|
// summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
|
|
@@ -441,16 +400,35 @@ class GarbageCollector {
|
|
|
441
400
|
*
|
|
442
401
|
* 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
|
|
443
402
|
*
|
|
444
|
-
* 3.
|
|
403
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
445
404
|
*
|
|
446
|
-
*
|
|
405
|
+
* 4. The GC version in the latest summary is different from the current GC version. This can happen if:
|
|
447
406
|
*
|
|
448
|
-
*
|
|
407
|
+
* 4.1. The summary this client loaded with has data from a different GC version.
|
|
408
|
+
*
|
|
409
|
+
* 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
449
410
|
*/
|
|
450
411
|
get summaryStateNeedsReset() {
|
|
451
|
-
return this.
|
|
412
|
+
return this.gcStateNeedsReset ||
|
|
452
413
|
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
|
|
453
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Tells whether the GC state needs to be reset. This can happen under 3 conditions:
|
|
417
|
+
*
|
|
418
|
+
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
419
|
+
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
420
|
+
*
|
|
421
|
+
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
422
|
+
* a document and the first time GC is enabled after is was disabled before.
|
|
423
|
+
*
|
|
424
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
425
|
+
*
|
|
426
|
+
* Note that the state will be reset only once for the first summary generated after this returns true. After that,
|
|
427
|
+
* this will return false.
|
|
428
|
+
*/
|
|
429
|
+
get gcStateNeedsReset() {
|
|
430
|
+
return this.wasGCRunInLatestSummary !== this.shouldRunGC;
|
|
431
|
+
}
|
|
454
432
|
/** Returns a list of all the configurations for garbage collection. */
|
|
455
433
|
get configs() {
|
|
456
434
|
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);
|
|
@@ -472,8 +450,63 @@ class GarbageCollector {
|
|
|
472
450
|
if (!this.tombstoneMode || (baseSnapshotData === null || baseSnapshotData === void 0 ? void 0 : baseSnapshotData.tombstones) === undefined) {
|
|
473
451
|
return;
|
|
474
452
|
}
|
|
475
|
-
this.tombstones = baseSnapshotData.tombstones;
|
|
476
|
-
this.runtime.
|
|
453
|
+
this.tombstones = Array.from(baseSnapshotData.tombstones);
|
|
454
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
|
|
458
|
+
* All current tracking is reset and updated from the data in the snapshot.
|
|
459
|
+
* @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
|
|
460
|
+
* is reset.
|
|
461
|
+
* @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
|
|
462
|
+
* timestamp.
|
|
463
|
+
*/
|
|
464
|
+
updateStateFromSnapshotData(snapshotData, currentReferenceTimestampMs) {
|
|
465
|
+
/**
|
|
466
|
+
* Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
|
|
467
|
+
* snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
|
|
468
|
+
* its refreshing state from a summary that happened at seq#900. In this case, there may be references between
|
|
469
|
+
* seq#901 and seq#1000 that we don't want to reset.
|
|
470
|
+
* Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
|
|
471
|
+
* references here. This should be fine because, in the worst case, we may end up updating the unreferenced
|
|
472
|
+
* timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
|
|
473
|
+
* scenarios, so it should be okay.
|
|
474
|
+
*/
|
|
475
|
+
// Clear all existing unreferenced state tracking.
|
|
476
|
+
for (const [, nodeStateTracker] of this.unreferencedNodesState) {
|
|
477
|
+
nodeStateTracker.stopTracking();
|
|
478
|
+
}
|
|
479
|
+
;
|
|
480
|
+
this.unreferencedNodesState.clear();
|
|
481
|
+
// If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
|
|
482
|
+
// container as per the state in the snapshot data.
|
|
483
|
+
if (this.tombstoneMode) {
|
|
484
|
+
this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
|
|
485
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
486
|
+
}
|
|
487
|
+
// If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
|
|
488
|
+
if (snapshotData === undefined) {
|
|
489
|
+
this.gcDataFromLastRun = undefined;
|
|
490
|
+
this.latestSummaryData = undefined;
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
|
|
494
|
+
// to the GC data from the snapshot data.
|
|
495
|
+
const gcNodes = {};
|
|
496
|
+
for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
|
|
497
|
+
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
498
|
+
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
499
|
+
}
|
|
500
|
+
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
501
|
+
}
|
|
502
|
+
this.gcDataFromLastRun = { gcNodes };
|
|
503
|
+
// If tracking state across summaries, update latest summary data from the snapshot's GC data.
|
|
504
|
+
if (this.trackGCState) {
|
|
505
|
+
this.latestSummaryData = {
|
|
506
|
+
serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
|
|
507
|
+
serializedTombstones: JSON.stringify(snapshotData.tombstones),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
477
510
|
}
|
|
478
511
|
/**
|
|
479
512
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -559,13 +592,13 @@ class GarbageCollector {
|
|
|
559
592
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
560
593
|
// involving access to deleted data.
|
|
561
594
|
if (this.testMode) {
|
|
562
|
-
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds
|
|
595
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
563
596
|
}
|
|
564
597
|
else if (this.tombstoneMode) {
|
|
565
|
-
// If we are running in GC tombstone mode,
|
|
566
|
-
//
|
|
567
|
-
// Note: we will not tombstone in test mode
|
|
568
|
-
this.runtime.
|
|
598
|
+
// If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
|
|
599
|
+
// involving access to "deleted" data without actually deleting the data from summaries.
|
|
600
|
+
// Note: we will not tombstone in test mode.
|
|
601
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
569
602
|
}
|
|
570
603
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
571
604
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
@@ -580,11 +613,11 @@ class GarbageCollector {
|
|
|
580
613
|
*/
|
|
581
614
|
summarize(fullTree, trackState, telemetryContext) {
|
|
582
615
|
var _a;
|
|
583
|
-
if (!this.shouldRunGC || this.
|
|
616
|
+
if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
|
|
584
617
|
return;
|
|
585
618
|
}
|
|
586
619
|
const gcState = { gcNodes: {} };
|
|
587
|
-
for (const [nodeId, outboundRoutes] of Object.entries(this.
|
|
620
|
+
for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
|
|
588
621
|
gcState.gcNodes[nodeId] = {
|
|
589
622
|
outboundRoutes,
|
|
590
623
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
@@ -609,7 +642,7 @@ class GarbageCollector {
|
|
|
609
642
|
return {
|
|
610
643
|
summary: {
|
|
611
644
|
type: protocol_definitions_1.SummaryType.Handle,
|
|
612
|
-
handle: `/${
|
|
645
|
+
handle: `/${runtime_definitions_1.gcTreeKey}`,
|
|
613
646
|
handleType: protocol_definitions_1.SummaryType.Tree,
|
|
614
647
|
},
|
|
615
648
|
stats,
|
|
@@ -633,11 +666,11 @@ class GarbageCollector {
|
|
|
633
666
|
*/
|
|
634
667
|
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
635
668
|
var _a, _b;
|
|
636
|
-
const gcStateBlobKey = `${
|
|
669
|
+
const gcStateBlobKey = `${runtime_definitions_1.gcBlobPrefix}_root`;
|
|
637
670
|
const builder = new runtime_utils_1.SummaryTreeBuilder();
|
|
638
671
|
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
639
672
|
if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
|
|
640
|
-
builder.addHandle(gcStateBlobKey, protocol_definitions_1.SummaryType.Blob, `/${
|
|
673
|
+
builder.addHandle(gcStateBlobKey, protocol_definitions_1.SummaryType.Blob, `/${runtime_definitions_1.gcTreeKey}/${gcStateBlobKey}`);
|
|
641
674
|
}
|
|
642
675
|
else {
|
|
643
676
|
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
@@ -648,10 +681,10 @@ class GarbageCollector {
|
|
|
648
681
|
}
|
|
649
682
|
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
650
683
|
if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
|
|
651
|
-
builder.addHandle(
|
|
684
|
+
builder.addHandle(runtime_definitions_1.gcTombstoneBlobKey, protocol_definitions_1.SummaryType.Blob, `/${runtime_definitions_1.gcTreeKey}/${runtime_definitions_1.gcTombstoneBlobKey}`);
|
|
652
685
|
}
|
|
653
686
|
else {
|
|
654
|
-
builder.addBlob(
|
|
687
|
+
builder.addBlob(runtime_definitions_1.gcTombstoneBlobKey, serializedTombstones);
|
|
655
688
|
}
|
|
656
689
|
return builder.getSummaryTree();
|
|
657
690
|
}
|
|
@@ -668,50 +701,58 @@ class GarbageCollector {
|
|
|
668
701
|
};
|
|
669
702
|
}
|
|
670
703
|
/**
|
|
671
|
-
* Returns a
|
|
672
|
-
*
|
|
704
|
+
* Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
|
|
705
|
+
* in the container.
|
|
673
706
|
*/
|
|
674
707
|
async getBaseGCDetails() {
|
|
675
708
|
return this.baseGCDetailsP;
|
|
676
709
|
}
|
|
677
710
|
/**
|
|
678
|
-
* Called
|
|
679
|
-
*
|
|
711
|
+
* Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
|
|
712
|
+
* is downloaded and should be used to update the state.
|
|
680
713
|
*/
|
|
681
|
-
async
|
|
682
|
-
|
|
714
|
+
async refreshLatestSummary(result, proposalHandle, summaryRefSeq, readAndParseBlob) {
|
|
715
|
+
// If the latest summary was updated and the summary was tracked, this client is the one that generated this
|
|
716
|
+
// summary. So, update wasGCRunInLatestSummary.
|
|
717
|
+
// Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
|
|
718
|
+
// true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
|
|
719
|
+
if (result.latestSummaryUpdated && result.wasSummaryTracked) {
|
|
720
|
+
this.wasGCRunInLatestSummary = this.shouldRunGC;
|
|
721
|
+
}
|
|
722
|
+
if (!result.latestSummaryUpdated || !this.shouldRunGC) {
|
|
683
723
|
return;
|
|
684
724
|
}
|
|
685
725
|
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
686
|
-
//
|
|
726
|
+
// Update latest state from pending.
|
|
687
727
|
if (result.wasSummaryTracked) {
|
|
688
728
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
689
|
-
this.initialStateNeedsReset = false;
|
|
690
729
|
if (this.trackGCState) {
|
|
691
730
|
this.latestSummaryData = this.pendingSummaryData;
|
|
692
731
|
this.pendingSummaryData = undefined;
|
|
693
732
|
}
|
|
694
733
|
return;
|
|
695
734
|
}
|
|
696
|
-
// If the summary was not tracked by this client,
|
|
697
|
-
// result as that is now the latest summary.
|
|
735
|
+
// If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
|
|
698
736
|
const snapshot = result.snapshot;
|
|
699
737
|
const metadataBlobId = snapshot.blobs[summaryFormat_1.metadataBlobName];
|
|
700
738
|
if (metadataBlobId) {
|
|
701
739
|
const metadata = await readAndParseBlob(metadataBlobId);
|
|
702
740
|
this.latestSummaryGCVersion = (0, summaryFormat_1.getGCVersion)(metadata);
|
|
703
741
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
710
|
-
};
|
|
742
|
+
// The current reference timestamp should be available if we are refreshing state from a snapshot. There has
|
|
743
|
+
// to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
|
|
744
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
745
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
746
|
+
throw container_utils_1.DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) });
|
|
711
747
|
}
|
|
712
|
-
|
|
713
|
-
|
|
748
|
+
const gcSnapshotTree = snapshot.trees[runtime_definitions_1.gcTreeKey];
|
|
749
|
+
// If GC ran in the container that generated this snapshot, it will have a GC tree.
|
|
750
|
+
this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
|
|
751
|
+
let latestGCData;
|
|
752
|
+
if (gcSnapshotTree !== undefined) {
|
|
753
|
+
latestGCData = await (0, garbage_collector_1.getGCDataFromSnapshot)(gcSnapshotTree, readAndParseBlob);
|
|
714
754
|
}
|
|
755
|
+
this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
|
|
715
756
|
this.pendingSummaryData = undefined;
|
|
716
757
|
}
|
|
717
758
|
/**
|
|
@@ -739,7 +780,7 @@ class GarbageCollector {
|
|
|
739
780
|
* @param toNodePath - The node to which the reference is added.
|
|
740
781
|
*/
|
|
741
782
|
addedOutboundReference(fromNodePath, toNodePath) {
|
|
742
|
-
var _a;
|
|
783
|
+
var _a, _b;
|
|
743
784
|
if (!this.shouldRunGC) {
|
|
744
785
|
return;
|
|
745
786
|
}
|
|
@@ -750,6 +791,23 @@ class GarbageCollector {
|
|
|
750
791
|
if (nodeStateTracker && nodeStateTracker.state !== exports.UnreferencedState.Active) {
|
|
751
792
|
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
752
793
|
}
|
|
794
|
+
if (this.tombstones.includes(toNodePath)) {
|
|
795
|
+
const nodeType = this.runtime.getNodeType(toNodePath);
|
|
796
|
+
let eventName = "GC_Tombstone_SubDatastore_Revived";
|
|
797
|
+
if (nodeType === exports.GCNodeType.DataStore) {
|
|
798
|
+
eventName = "GC_Tombstone_Datastore_Revived";
|
|
799
|
+
}
|
|
800
|
+
else if (nodeType === exports.GCNodeType.Blob) {
|
|
801
|
+
eventName = "GC_Tombstone_Blob_Revived";
|
|
802
|
+
}
|
|
803
|
+
this.mc.logger.sendTelemetryEvent({
|
|
804
|
+
eventName,
|
|
805
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
806
|
+
url: (0, garbage_collector_1.trimLeadingSlashes)(toNodePath),
|
|
807
|
+
nodeType,
|
|
808
|
+
throwOnTombstoneUsage: (_b = this.mc.config.getBoolean(garbageCollectionConstants_1.throwOnTombstoneUsageKey)) !== null && _b !== void 0 ? _b : false,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
753
811
|
}
|
|
754
812
|
dispose() {
|
|
755
813
|
var _a;
|
|
@@ -766,7 +824,7 @@ class GarbageCollector {
|
|
|
766
824
|
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
767
825
|
*/
|
|
768
826
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
769
|
-
this.
|
|
827
|
+
this.gcDataFromLastRun = (0, garbage_collector_1.cloneGCData)(gcData);
|
|
770
828
|
this.tombstones = [];
|
|
771
829
|
this.newReferencesSinceLastRun.clear();
|
|
772
830
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -802,27 +860,31 @@ class GarbageCollector {
|
|
|
802
860
|
}
|
|
803
861
|
/**
|
|
804
862
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
805
|
-
* time.
|
|
806
|
-
*
|
|
863
|
+
* time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
|
|
864
|
+
* updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
|
|
865
|
+
* these objects while there can be in-memory referenced to it:
|
|
866
|
+
* 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
|
|
867
|
+
* added, the object may have been accessed and in-memory reference to it added.
|
|
868
|
+
* 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
|
|
869
|
+
* unreferenced, they could have been accessed and in-memory reference to them added.
|
|
807
870
|
*
|
|
808
871
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
809
872
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
810
873
|
*/
|
|
811
874
|
updateStateSinceLastRun(currentGCData, logger) {
|
|
812
875
|
// If we haven't run GC before there is nothing to do.
|
|
813
|
-
if (this.
|
|
876
|
+
if (this.gcDataFromLastRun === undefined) {
|
|
814
877
|
return;
|
|
815
878
|
}
|
|
816
879
|
// Find any references that haven't been identified correctly.
|
|
817
|
-
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.
|
|
880
|
+
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
|
|
818
881
|
if (missingExplicitReferences.length > 0) {
|
|
819
882
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
820
|
-
|
|
883
|
+
logger.sendErrorEvent({
|
|
821
884
|
eventName: "gcUnknownOutboundReferences",
|
|
822
885
|
gcNodeId: missingExplicitReference[0],
|
|
823
886
|
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
824
|
-
};
|
|
825
|
-
logger.sendPerformanceEvent(event);
|
|
887
|
+
});
|
|
826
888
|
});
|
|
827
889
|
}
|
|
828
890
|
// No references were added since the last run so we don't have to update reference states of any unreferenced
|
|
@@ -836,21 +898,18 @@ class GarbageCollector {
|
|
|
836
898
|
* run, and then add the references since last run.
|
|
837
899
|
*
|
|
838
900
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
839
|
-
*
|
|
840
901
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
841
|
-
* references added new outbound references before
|
|
902
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
842
903
|
*
|
|
843
904
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
844
|
-
* references added new outbound references before
|
|
905
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
845
906
|
*
|
|
846
907
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
847
|
-
*
|
|
848
|
-
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
849
|
-
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
850
|
-
*
|
|
908
|
+
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
851
909
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
852
910
|
*/
|
|
853
|
-
const gcDataSuperSet = (0, garbage_collector_1.concatGarbageCollectionData)(this.
|
|
911
|
+
const gcDataSuperSet = (0, garbage_collector_1.concatGarbageCollectionData)(this.gcDataFromLastRun, currentGCData);
|
|
912
|
+
const newOutboundRoutesSinceLastRun = [];
|
|
854
913
|
this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
|
|
855
914
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
856
915
|
gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
|
|
@@ -858,19 +917,22 @@ class GarbageCollector {
|
|
|
858
917
|
else {
|
|
859
918
|
gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
|
|
860
919
|
}
|
|
920
|
+
newOutboundRoutesSinceLastRun.push(...outboundRoutes);
|
|
861
921
|
});
|
|
862
922
|
/**
|
|
863
|
-
* Run GC on the above reference graph
|
|
923
|
+
* Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
|
|
924
|
+
* list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
|
|
864
925
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
865
|
-
*
|
|
926
|
+
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
927
|
+
* unreferenced and add unreferenced state.
|
|
866
928
|
*/
|
|
867
|
-
const gcResult = (0, garbage_collector_1.runGarbageCollection)(gcDataSuperSet.gcNodes, ["/"]);
|
|
929
|
+
const gcResult = (0, garbage_collector_1.runGarbageCollection)(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
|
|
868
930
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
869
931
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
870
932
|
if (nodeStateTracker !== undefined) {
|
|
871
933
|
// Stop tracking so as to clear out any running timers.
|
|
872
934
|
nodeStateTracker.stopTracking();
|
|
873
|
-
// Delete the
|
|
935
|
+
// Delete the unreferenced state as we don't need to track it any more.
|
|
874
936
|
this.unreferencedNodesState.delete(nodeId);
|
|
875
937
|
}
|
|
876
938
|
}
|
|
@@ -897,14 +959,19 @@ class GarbageCollector {
|
|
|
897
959
|
const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
|
|
898
960
|
const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
|
|
899
961
|
const missingExplicitRoutes = [];
|
|
962
|
+
/**
|
|
963
|
+
* 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
|
|
964
|
+
* explicit references should be added to missing explicit routes list.
|
|
965
|
+
* 2. Only include data store and blob routes since GC only works for these two.
|
|
966
|
+
* Note: Due to a bug with de-duped blobs, only adding data store routes for now.
|
|
967
|
+
* 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
|
|
968
|
+
* explicit routes to them.
|
|
969
|
+
*/
|
|
900
970
|
currentOutboundRoutes.forEach((route) => {
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (isBlobOrDataStoreRoute &&
|
|
906
|
-
notRouteFromDDSToParentDataStore &&
|
|
907
|
-
(!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
|
|
971
|
+
const nodeType = this.runtime.getNodeType(route);
|
|
972
|
+
if ((nodeType === exports.GCNodeType.DataStore || nodeType === exports.GCNodeType.Blob)
|
|
973
|
+
&& !nodeId.startsWith(route)
|
|
974
|
+
&& (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
|
|
908
975
|
missingExplicitRoutes.push(route);
|
|
909
976
|
}
|
|
910
977
|
});
|
|
@@ -936,7 +1003,7 @@ class GarbageCollector {
|
|
|
936
1003
|
gcStats.nodeCount++;
|
|
937
1004
|
// If there is no previous GC data, every node's state is generated and is considered as updated.
|
|
938
1005
|
// Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
|
|
939
|
-
const stateUpdated = this.
|
|
1006
|
+
const stateUpdated = this.gcDataFromLastRun === undefined ||
|
|
940
1007
|
this.unreferencedNodesState.has(nodeId) === referenced;
|
|
941
1008
|
if (stateUpdated) {
|
|
942
1009
|
gcStats.updatedNodeCount++;
|
|
@@ -1043,7 +1110,15 @@ class GarbageCollector {
|
|
|
1043
1110
|
// Events generated:
|
|
1044
1111
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1045
1112
|
if (usageType === "Loaded") {
|
|
1046
|
-
|
|
1113
|
+
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: (0, runtime_utils_1.packagePathToTelemetryProperty)(packagePath), stack: (0, telemetry_utils_1.generateStack)() });
|
|
1114
|
+
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
1115
|
+
// detecting something wrong with GC either from the partner or from the runtime itself.
|
|
1116
|
+
if (state === exports.UnreferencedState.Inactive) {
|
|
1117
|
+
this.mc.logger.sendTelemetryEvent(event);
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
this.mc.logger.sendErrorEvent(event);
|
|
1121
|
+
}
|
|
1047
1122
|
}
|
|
1048
1123
|
// If SweepReady Usage Detection is enabed, the handler may close the interactive container.
|
|
1049
1124
|
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
@@ -1072,40 +1147,19 @@ class GarbageCollector {
|
|
|
1072
1147
|
if ((usageType === "Revived") === active) {
|
|
1073
1148
|
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
1074
1149
|
const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
|
|
1075
|
-
|
|
1150
|
+
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg ? { value: pkg.join("/"), tag: telemetry_utils_1.TelemetryDataTag.CodeArtifact } : undefined, fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: telemetry_utils_1.TelemetryDataTag.CodeArtifact } : undefined });
|
|
1151
|
+
if (state === exports.UnreferencedState.Inactive) {
|
|
1152
|
+
logger.sendTelemetryEvent(event);
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
logger.sendErrorEvent(event);
|
|
1156
|
+
}
|
|
1076
1157
|
}
|
|
1077
1158
|
}
|
|
1078
1159
|
this.pendingEventsQueue = [];
|
|
1079
1160
|
}
|
|
1080
1161
|
}
|
|
1081
1162
|
exports.GarbageCollector = GarbageCollector;
|
|
1082
|
-
/**
|
|
1083
|
-
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1084
|
-
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1085
|
-
*/
|
|
1086
|
-
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1087
|
-
let rootGCState = { gcNodes: {} };
|
|
1088
|
-
let tombstones;
|
|
1089
|
-
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1090
|
-
if (key === garbageCollectionConstants_1.gcTombstoneBlobKey) {
|
|
1091
|
-
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1092
|
-
continue;
|
|
1093
|
-
}
|
|
1094
|
-
// Skip blobs that do not start with the GC prefix.
|
|
1095
|
-
if (!key.startsWith(garbageCollectionConstants_1.gcBlobPrefix)) {
|
|
1096
|
-
continue;
|
|
1097
|
-
}
|
|
1098
|
-
const blobId = gcSnapshotTree.blobs[key];
|
|
1099
|
-
if (blobId === undefined) {
|
|
1100
|
-
continue;
|
|
1101
|
-
}
|
|
1102
|
-
const gcState = await readAndParseBlob(blobId);
|
|
1103
|
-
(0, common_utils_1.assert)(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
|
|
1104
|
-
// Merge the GC state of this blob into the root GC state.
|
|
1105
|
-
rootGCState = (0, garbage_collector_1.concatGarbageCollectionStates)(rootGCState, gcState);
|
|
1106
|
-
}
|
|
1107
|
-
return { gcState: rootGCState, tombstones };
|
|
1108
|
-
}
|
|
1109
1163
|
function generateSortedGCState(gcState) {
|
|
1110
1164
|
const sortableArray = Object.entries(gcState.gcNodes);
|
|
1111
1165
|
sortableArray.sort(([a], [b]) => a.localeCompare(b));
|