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