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