@fluidframework/container-runtime 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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/CHANGELOG.md +4 -0
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +3 -2
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +1 -0
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +20 -11
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +1 -2
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +5 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -6
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +56 -70
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +227 -408
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +8 -10
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +2 -0
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts +11 -11
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +18 -22
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +6 -2
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +16 -6
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +91 -0
- package/dist/gc/gcTelemetry.d.ts.map +1 -0
- package/dist/gc/gcTelemetry.js +282 -0
- package/dist/gc/gcTelemetry.js.map +1 -0
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +5 -6
- package/dist/gc/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +1 -1
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.js +1 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +25 -22
- 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/pendingStateManager.d.ts +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.js +15 -4
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js +14 -17
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/summarizer.d.ts +2 -0
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +9 -4
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerHeuristics.d.ts +8 -9
- package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summary/summarizerHeuristics.js +15 -16
- package/dist/summary/summarizerHeuristics.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +2 -0
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js +4 -3
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +3 -2
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +1 -0
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +20 -11
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +5 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -6
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +56 -70
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +231 -412
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +8 -10
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +2 -0
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts +11 -11
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +16 -20
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +6 -2
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +16 -6
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +91 -0
- package/lib/gc/gcTelemetry.d.ts.map +1 -0
- package/lib/gc/gcTelemetry.js +277 -0
- package/lib/gc/gcTelemetry.js.map +1 -0
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +1 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.js +1 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +25 -22
- 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/pendingStateManager.d.ts +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.js +15 -4
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js +14 -17
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/summarizer.d.ts +2 -0
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +9 -4
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerHeuristics.d.ts +8 -9
- package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summary/summarizerHeuristics.js +15 -16
- package/lib/summary/summarizerHeuristics.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +2 -0
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js +4 -3
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/package.json +15 -16
- package/src/blobManager.ts +3 -2
- package/src/connectionTelemetry.ts +1 -0
- package/src/containerRuntime.ts +22 -15
- package/src/dataStoreContext.ts +1 -2
- package/src/dataStores.ts +4 -7
- package/src/gc/garbageCollection.ts +316 -561
- package/src/gc/gcConfigs.ts +12 -11
- package/src/gc/gcDefinitions.ts +2 -0
- package/src/gc/gcHelpers.ts +21 -40
- package/src/gc/gcSummaryStateTracker.ts +19 -7
- package/src/gc/gcTelemetry.ts +408 -0
- package/src/gc/index.ts +2 -6
- package/src/opLifecycle/README.md +13 -0
- package/src/opLifecycle/opGroupingManager.ts +1 -1
- package/src/opLifecycle/outbox.ts +2 -2
- package/src/opLifecycle/remoteMessageProcessor.ts +37 -28
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +1 -4
- package/src/scheduleManager.ts +19 -7
- package/src/summary/orderedClientElection.ts +14 -17
- package/src/summary/summarizer.ts +17 -5
- package/src/summary/summarizerHeuristics.ts +15 -16
- package/src/summary/summarizerTypes.ts +2 -0
- package/src/summary/summaryGenerator.ts +5 -4
- package/dist/gc/gcSweepReadyUsageDetection.d.ts +0 -53
- package/dist/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
- package/dist/gc/gcSweepReadyUsageDetection.js +0 -130
- package/dist/gc/gcSweepReadyUsageDetection.js.map +0 -1
- package/lib/gc/gcSweepReadyUsageDetection.d.ts +0 -53
- package/lib/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
- package/lib/gc/gcSweepReadyUsageDetection.js +0 -125
- package/lib/gc/gcSweepReadyUsageDetection.js.map +0 -1
- package/src/gc/gcSweepReadyUsageDetection.ts +0 -145
|
@@ -2,29 +2,18 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
var t = {};
|
|
7
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
8
|
-
t[p] = s[p];
|
|
9
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
10
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
11
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
12
|
-
t[p[i]] = s[p[i]];
|
|
13
|
-
}
|
|
14
|
-
return t;
|
|
15
|
-
};
|
|
16
|
-
import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
5
|
+
import { LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
17
6
|
import { ClientSessionExpiredError, DataProcessingError } from "@fluidframework/container-utils";
|
|
18
7
|
import { gcTreeKey, } from "@fluidframework/runtime-definitions";
|
|
19
|
-
import {
|
|
20
|
-
import { ChildLogger, generateStack, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
|
|
8
|
+
import { ChildLogger, loggerToMonitoringContext, PerformanceEvent, } from "@fluidframework/telemetry-utils";
|
|
21
9
|
import { RuntimeHeaders } from "../containerRuntime";
|
|
22
10
|
import { generateGCConfigs } from "./gcConfigs";
|
|
23
|
-
import {
|
|
24
|
-
import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot
|
|
11
|
+
import { GCNodeType, UnreferencedState, } from "./gcDefinitions";
|
|
12
|
+
import { cloneGCData, concatGarbageCollectionData, getGCDataFromSnapshot } from "./gcHelpers";
|
|
25
13
|
import { runGarbageCollection } from "./gcReferenceGraphAlgorithm";
|
|
26
14
|
import { GCSummaryStateTracker } from "./gcSummaryStateTracker";
|
|
27
15
|
import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
16
|
+
import { GCTelemetryTracker } from "./gcTelemetry";
|
|
28
17
|
/**
|
|
29
18
|
* The garbage collector for the container runtime. It consolidates the garbage collection functionality and maintains
|
|
30
19
|
* its state across summaries.
|
|
@@ -58,16 +47,10 @@ export class GarbageCollector {
|
|
|
58
47
|
this.deletedNodes = new Set();
|
|
59
48
|
// Map of node ids to their unreferenced state tracker.
|
|
60
49
|
this.unreferencedNodesState = new Map();
|
|
61
|
-
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
62
|
-
// per event per node.
|
|
63
|
-
this.loggedUnreferencedEvents = new Set();
|
|
64
|
-
// Queue for unreferenced events that should be logged the next time GC runs.
|
|
65
|
-
this.pendingEventsQueue = [];
|
|
66
50
|
// The number of times GC has successfully completed on this instance of GarbageCollector.
|
|
67
51
|
this.completedRuns = 0;
|
|
68
52
|
this.runtime = createParams.runtime;
|
|
69
53
|
this.isSummarizerClient = createParams.isSummarizerClient;
|
|
70
|
-
this.createContainerMetadata = createParams.createContainerMetadata;
|
|
71
54
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
72
55
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
73
56
|
this.activeConnection = createParams.activeConnection;
|
|
@@ -88,6 +71,7 @@ export class GarbageCollector {
|
|
|
88
71
|
this.sessionExpiryTimer.start();
|
|
89
72
|
}
|
|
90
73
|
this.summaryStateTracker = new GCSummaryStateTracker(this.configs, (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined /* wasGCRunInBaseSnapshot */);
|
|
74
|
+
this.telemetryTracker = new GCTelemetryTracker(this.mc, this.configs, this.isSummarizerClient, this.runtime.gcTombstoneEnforcementAllowed, createParams.createContainerMetadata, (nodeId) => this.runtime.getNodeType(nodeId), (nodeId) => this.unreferencedNodesState.get(nodeId), this.getNodePackagePath);
|
|
91
75
|
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
92
76
|
// it involves fetching blobs from storage which is expensive.
|
|
93
77
|
this.baseSnapshotDataP = new LazyPromise(async () => {
|
|
@@ -107,8 +91,7 @@ export class GarbageCollector {
|
|
|
107
91
|
// in the snapshot cannot be interpreted correctly. Set everything to undefined except for
|
|
108
92
|
// deletedNodes because irrespective of GC versions, these nodes have been deleted and cannot be
|
|
109
93
|
// brought back. The deletedNodes info is needed to identify when these nodes are used.
|
|
110
|
-
if (this.configs.gcVersionInBaseSnapshot
|
|
111
|
-
this.summaryStateTracker.currentGCVersion) {
|
|
94
|
+
if (this.configs.gcVersionInEffect !== this.configs.gcVersionInBaseSnapshot) {
|
|
112
95
|
return {
|
|
113
96
|
gcState: undefined,
|
|
114
97
|
tombstones: undefined,
|
|
@@ -197,6 +180,10 @@ export class GarbageCollector {
|
|
|
197
180
|
get summaryStateNeedsReset() {
|
|
198
181
|
return this.summaryStateTracker.doesSummaryStateNeedReset;
|
|
199
182
|
}
|
|
183
|
+
/** Returns the count of data stores whose GC state updated since the last summary. */
|
|
184
|
+
get updatedDSCountSinceLastSummary() {
|
|
185
|
+
return this.summaryStateTracker.updatedDSCountSinceLastSummary;
|
|
186
|
+
}
|
|
200
187
|
/**
|
|
201
188
|
* Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
|
|
202
189
|
* during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
|
|
@@ -318,6 +305,13 @@ export class GarbageCollector {
|
|
|
318
305
|
this.initializeGCStateFromBaseSnapshotP.catch((error) => { });
|
|
319
306
|
}
|
|
320
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
|
|
310
|
+
* in the container.
|
|
311
|
+
*/
|
|
312
|
+
async getBaseGCDetails() {
|
|
313
|
+
return this.baseGCDetailsP;
|
|
314
|
+
}
|
|
321
315
|
/**
|
|
322
316
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
323
317
|
* @returns stats of the GC run or undefined if GC did not run.
|
|
@@ -353,200 +347,67 @@ export class GarbageCollector {
|
|
|
353
347
|
return undefined;
|
|
354
348
|
}
|
|
355
349
|
return PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
350
|
+
/** Pre-GC steps */
|
|
351
|
+
// Ensure that state has been initialized from the base snapshot data.
|
|
352
|
+
await this.initializeGCStateFromBaseSnapshotP;
|
|
353
|
+
// Let the runtime update its pending state before GC runs.
|
|
354
|
+
await this.runtime.updateStateBeforeGC();
|
|
355
|
+
/** GC step */
|
|
356
|
+
const gcStats = await this.runGC(fullGC, currentReferenceTimestampMs, logger);
|
|
361
357
|
event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
|
|
358
|
+
/** Post-GC steps */
|
|
359
|
+
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
360
|
+
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
361
|
+
// reference to an unreferenced node from another unreferenced node which means the node wasn't revived.
|
|
362
|
+
await this.telemetryTracker.logPendingEvents(logger);
|
|
363
|
+
// Update the state of summary state tracker from this run's stats.
|
|
364
|
+
this.summaryStateTracker.updateStateFromGCRunStats(gcStats);
|
|
365
|
+
this.newReferencesSinceLastRun.clear();
|
|
362
366
|
this.completedRuns++;
|
|
363
367
|
return gcStats;
|
|
364
368
|
}, { end: true, cancel: "error" });
|
|
365
369
|
}
|
|
366
|
-
async runPreGCSteps() {
|
|
367
|
-
// Ensure that state has been initialized from the base snapshot data.
|
|
368
|
-
await this.initializeGCStateFromBaseSnapshotP;
|
|
369
|
-
// Let the runtime update its pending state before GC runs.
|
|
370
|
-
await this.runtime.updateStateBeforeGC();
|
|
371
|
-
}
|
|
372
|
-
async runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs) {
|
|
373
|
-
// Generate statistics from the current run. This is done before updating the current state because it
|
|
374
|
-
// generates some of its data based on previous state of the system.
|
|
375
|
-
const gcStats = this.generateStats(gcResult);
|
|
376
|
-
// Update the current mark state and update the runtime of all used routes or ids that used as per the GC run.
|
|
377
|
-
const sweepReadyNodes = this.updateMarkPhase(gcData, gcResult, currentReferenceTimestampMs, logger);
|
|
378
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
379
|
-
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
380
|
-
// delete these objects here instead.
|
|
381
|
-
this.logSweepEvents(logger, currentReferenceTimestampMs);
|
|
382
|
-
let updatedGCData = gcData;
|
|
383
|
-
if (this.configs.shouldRunSweep) {
|
|
384
|
-
updatedGCData = this.runSweepPhase(sweepReadyNodes, gcData);
|
|
385
|
-
}
|
|
386
|
-
else if (this.configs.testMode) {
|
|
387
|
-
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
388
|
-
// involving access to deleted data.
|
|
389
|
-
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
390
|
-
}
|
|
391
|
-
else if (this.configs.tombstoneMode) {
|
|
392
|
-
this.tombstones = sweepReadyNodes;
|
|
393
|
-
// If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
|
|
394
|
-
// involving access to "deleted" data without actually deleting the data from summaries.
|
|
395
|
-
// Note: we will not tombstone in test mode.
|
|
396
|
-
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
397
|
-
}
|
|
398
|
-
this.gcDataFromLastRun = cloneGCData(updatedGCData);
|
|
399
|
-
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
400
|
-
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
401
|
-
// reference to an unreferenced node from another unreferenced node which means the node wasn't revived.
|
|
402
|
-
await this.logUnreferencedEvents(logger);
|
|
403
|
-
return gcStats;
|
|
404
|
-
}
|
|
405
370
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
371
|
+
* Runs garbage collection. It does the following:
|
|
372
|
+
* 1. It generates / analyzes the runtime's reference graph.
|
|
373
|
+
* 2. Generates stats for the GC run based on previous / current GC state.
|
|
374
|
+
* 3. Runs Mark phase.
|
|
375
|
+
* 4. Runs Sweep phase.
|
|
409
376
|
*/
|
|
410
|
-
|
|
377
|
+
async runGC(fullGC, currentReferenceTimestampMs, logger) {
|
|
411
378
|
var _a;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
return
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
*/
|
|
430
|
-
gcFeature: this.configs.gcEnabled ? this.summaryStateTracker.currentGCVersion : 0,
|
|
431
|
-
gcFeatureMatrix: this.configs.persistedGcFeatureMatrix,
|
|
432
|
-
sessionExpiryTimeoutMs: this.configs.sessionExpiryTimeoutMs,
|
|
433
|
-
sweepEnabled: false,
|
|
434
|
-
sweepTimeoutMs: this.configs.sweepTimeoutMs,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
|
|
439
|
-
* in the container.
|
|
440
|
-
*/
|
|
441
|
-
async getBaseGCDetails() {
|
|
442
|
-
return this.baseGCDetailsP;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
|
|
446
|
-
* is downloaded and should be used to update the state.
|
|
447
|
-
*/
|
|
448
|
-
async refreshLatestSummary(proposalHandle, result, readAndParseBlob) {
|
|
449
|
-
const latestSnapshotData = await this.summaryStateTracker.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
|
|
450
|
-
// If the latest summary was updated but it was not tracked by this client, our state needs to be updated from
|
|
451
|
-
// this snapshot data.
|
|
452
|
-
if (this.shouldRunGC && result.latestSummaryUpdated && !result.wasSummaryTracked) {
|
|
453
|
-
// The current reference timestamp should be available if we are refreshing state from a snapshot. There has
|
|
454
|
-
// to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
|
|
455
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
456
|
-
if (currentReferenceTimestampMs === undefined) {
|
|
457
|
-
throw DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, {
|
|
458
|
-
proposalHandle,
|
|
459
|
-
summaryRefSeq: result.summaryRefSeq,
|
|
460
|
-
gcConfigs: JSON.stringify(this.configs),
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
this.updateStateFromSnapshotData(latestSnapshotData, currentReferenceTimestampMs);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
468
|
-
* @param nodePath - The id of the node that changed.
|
|
469
|
-
* @param reason - Whether the node was loaded or changed.
|
|
470
|
-
* @param timestampMs - The timestamp when the node changed.
|
|
471
|
-
* @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
|
|
472
|
-
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
473
|
-
*/
|
|
474
|
-
nodeUpdated(nodePath, reason, timestampMs, packagePath, requestHeaders) {
|
|
475
|
-
if (!this.configs.shouldRunGC) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
const nodeStateTracker = this.unreferencedNodesState.get(nodePath);
|
|
479
|
-
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
480
|
-
this.inactiveNodeUsed(reason, nodePath, nodeStateTracker, undefined /* fromNodeId */, packagePath, timestampMs, requestHeaders);
|
|
481
|
-
}
|
|
379
|
+
// 1. Generate / analyze the runtime's reference graph.
|
|
380
|
+
// Get the reference graph (gcData) and run GC algorithm to get referenced / unreferenced nodes.
|
|
381
|
+
const gcData = await this.runtime.getGCData(fullGC);
|
|
382
|
+
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
383
|
+
// Get all referenced nodes - References in this run + references between the previous and current runs.
|
|
384
|
+
const allReferencedNodeIds = (_a = this.findAllNodesReferencedBetweenGCs(gcData, this.gcDataFromLastRun, logger)) !== null && _a !== void 0 ? _a : gcResult.referencedNodeIds;
|
|
385
|
+
// 2. Generate stats based on the previous / current GC state.
|
|
386
|
+
// Must happen before running Mark / Sweep phase because previous GC state will be updated in these stages.
|
|
387
|
+
const gcStats = this.generateStats(gcResult);
|
|
388
|
+
// 3. Run the Mark phase.
|
|
389
|
+
// It will mark nodes as referenced / unreferenced and return a list of node ids that are ready to be swept.
|
|
390
|
+
const sweepReadyNodeIds = this.runMarkPhase(gcResult, allReferencedNodeIds, currentReferenceTimestampMs);
|
|
391
|
+
// 4. Run the Sweep phase.
|
|
392
|
+
// It will delete sweep ready nodes and return a list of deleted node ids.
|
|
393
|
+
const deletedNodeIds = this.runSweepPhase(gcResult, sweepReadyNodeIds, currentReferenceTimestampMs, logger);
|
|
394
|
+
this.gcDataFromLastRun = cloneGCData(gcData, (id) => deletedNodeIds.includes(id) /* filter out deleted nodes */);
|
|
395
|
+
return gcStats;
|
|
482
396
|
}
|
|
483
397
|
/**
|
|
484
|
-
*
|
|
485
|
-
* referenced
|
|
398
|
+
* Runs the GC Mark phase. It does the following:
|
|
399
|
+
* 1. Marks all referenced nodes in this run by clearing tracking for them.
|
|
400
|
+
* 2. Marks unreferenced nodes in this run by starting tracking for them.
|
|
401
|
+
* 3. Calls the runtime to update nodes that were marked referenced.
|
|
486
402
|
*
|
|
487
|
-
* @param fromNodePath - The node from which the reference is added.
|
|
488
|
-
* @param toNodePath - The node to which the reference is added.
|
|
489
|
-
*/
|
|
490
|
-
addedOutboundReference(fromNodePath, toNodePath) {
|
|
491
|
-
var _a;
|
|
492
|
-
if (!this.configs.shouldRunGC) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
const outboundRoutes = (_a = this.newReferencesSinceLastRun.get(fromNodePath)) !== null && _a !== void 0 ? _a : [];
|
|
496
|
-
outboundRoutes.push(toNodePath);
|
|
497
|
-
this.newReferencesSinceLastRun.set(fromNodePath, outboundRoutes);
|
|
498
|
-
const nodeStateTracker = this.unreferencedNodesState.get(toNodePath);
|
|
499
|
-
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
500
|
-
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
501
|
-
}
|
|
502
|
-
if (this.tombstones.includes(toNodePath)) {
|
|
503
|
-
const nodeType = this.runtime.getNodeType(toNodePath);
|
|
504
|
-
let eventName = "GC_Tombstone_SubDatastore_Revived";
|
|
505
|
-
if (nodeType === GCNodeType.DataStore) {
|
|
506
|
-
eventName = "GC_Tombstone_Datastore_Revived";
|
|
507
|
-
}
|
|
508
|
-
else if (nodeType === GCNodeType.Blob) {
|
|
509
|
-
eventName = "GC_Tombstone_Blob_Revived";
|
|
510
|
-
}
|
|
511
|
-
sendGCUnexpectedUsageEvent(this.mc, {
|
|
512
|
-
eventName,
|
|
513
|
-
category: "generic",
|
|
514
|
-
url: toNodePath,
|
|
515
|
-
nodeType,
|
|
516
|
-
gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
|
|
517
|
-
}, undefined /* packagePath */);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
|
|
522
|
-
* cases where objects are used after they are deleted and throw / log errors accordingly.
|
|
523
|
-
*/
|
|
524
|
-
isNodeDeleted(nodePath) {
|
|
525
|
-
return this.deletedNodes.has(nodePath);
|
|
526
|
-
}
|
|
527
|
-
dispose() {
|
|
528
|
-
var _a;
|
|
529
|
-
(_a = this.sessionExpiryTimer) === null || _a === void 0 ? void 0 : _a.clear();
|
|
530
|
-
this.sessionExpiryTimer = undefined;
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Updates the state of the system as per the current GC run. It does the following:
|
|
534
|
-
* 1. Sets up the current GC state as per the gcData.
|
|
535
|
-
* 2. Starts tracking for nodes that have become unreferenced in this run.
|
|
536
|
-
* 3. Clears tracking for nodes that were unreferenced but became referenced in this run.
|
|
537
|
-
* @param gcData - The data representing the reference graph on which GC is run.
|
|
538
403
|
* @param gcResult - The result of the GC run on the gcData.
|
|
404
|
+
* @param allReferencedNodeIds - Nodes referenced in this GC run + referenced between previous and current GC run.
|
|
539
405
|
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
540
|
-
* @returns - A list of sweep ready nodes.
|
|
406
|
+
* @returns - A list of sweep ready nodes, i.e., nodes that ready to be deleted.
|
|
541
407
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
// node's state
|
|
546
|
-
const allNodesReferencedBetweenGCs = (_a = this.findAllNodesReferencedBetweenGCs(gcData, this.gcDataFromLastRun, logger)) !== null && _a !== void 0 ? _a : gcResult.referencedNodeIds;
|
|
547
|
-
this.newReferencesSinceLastRun.clear();
|
|
548
|
-
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
549
|
-
for (const nodeId of allNodesReferencedBetweenGCs) {
|
|
408
|
+
runMarkPhase(gcResult, allReferencedNodeIds, currentReferenceTimestampMs) {
|
|
409
|
+
// 1. Marks all referenced nodes by clearing their unreferenced tracker, if any.
|
|
410
|
+
for (const nodeId of allReferencedNodeIds) {
|
|
550
411
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
551
412
|
if (nodeStateTracker !== undefined) {
|
|
552
413
|
// Stop tracking so as to clear out any running timers.
|
|
@@ -555,38 +416,72 @@ export class GarbageCollector {
|
|
|
555
416
|
this.unreferencedNodesState.delete(nodeId);
|
|
556
417
|
}
|
|
557
418
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
* If a node was already unreferenced, update its tracking information. Since the current reference time is
|
|
561
|
-
* from the ops seen, this will ensure that we keep updating the unreferenced state as time moves forward.
|
|
562
|
-
*
|
|
563
|
-
* If a node is sweep ready, store and then return it.
|
|
564
|
-
*/
|
|
565
|
-
const sweepReadyNodes = [];
|
|
419
|
+
// 2. Mark unreferenced nodes in this run by starting unreferenced tracking for them.
|
|
420
|
+
const sweepReadyNodeIds = [];
|
|
566
421
|
for (const nodeId of gcResult.deletedNodeIds) {
|
|
567
422
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
568
423
|
if (nodeStateTracker === undefined) {
|
|
569
424
|
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(currentReferenceTimestampMs, this.configs.inactiveTimeoutMs, currentReferenceTimestampMs, this.configs.sweepTimeoutMs));
|
|
570
425
|
}
|
|
571
426
|
else {
|
|
427
|
+
// If a node was already unreferenced, update its tracking information. Since the current reference time
|
|
428
|
+
// is from the ops seen, this will ensure that we keep updating unreferenced state as time moves forward.
|
|
572
429
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
430
|
+
// If a node is sweep ready, store it so it can be returned.
|
|
573
431
|
if (nodeStateTracker.state === UnreferencedState.SweepReady) {
|
|
574
|
-
|
|
432
|
+
sweepReadyNodeIds.push(nodeId);
|
|
575
433
|
}
|
|
576
434
|
}
|
|
577
435
|
}
|
|
578
|
-
|
|
436
|
+
// 3. Call the runtime to update referenced nodes in this run.
|
|
437
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
438
|
+
return sweepReadyNodeIds;
|
|
579
439
|
}
|
|
580
440
|
/**
|
|
581
|
-
*
|
|
582
|
-
*
|
|
441
|
+
* Runs the GC Sweep phase. It does the following:
|
|
442
|
+
* 1. Calls the runtime to delete nodes that are sweep ready.
|
|
443
|
+
* 2. Clears tracking for deleted nodes.
|
|
444
|
+
*
|
|
445
|
+
* @param gcResult - The result of the GC run on the gcData.
|
|
446
|
+
* @param sweepReadyNodes - List of nodes that are sweep ready.
|
|
447
|
+
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
448
|
+
* @param logger - The logger to be used to log any telemetry.
|
|
449
|
+
* @returns - A list of nodes that have been deleted.
|
|
583
450
|
*/
|
|
584
|
-
runSweepPhase(sweepReadyNodes,
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
451
|
+
runSweepPhase(gcResult, sweepReadyNodes, currentReferenceTimestampMs, logger) {
|
|
452
|
+
// Log events for objects that are ready to be deleted by sweep. This will give us data on sweep when
|
|
453
|
+
// its not enabled.
|
|
454
|
+
this.telemetryTracker.logSweepEvents(logger, currentReferenceTimestampMs, this.unreferencedNodesState, this.completedRuns, this.getLastSummaryTimestampMs());
|
|
455
|
+
/**
|
|
456
|
+
* Currently, there are 3 modes for sweep:
|
|
457
|
+
* Test mode - Unreferenced nodes are immediately deleted without waiting for them to be sweep ready.
|
|
458
|
+
* Tombstone mode - Sweep ready modes are marked as tombstones instead of being deleted.
|
|
459
|
+
* Sweep mode - Sweep ready modes are deleted.
|
|
460
|
+
*
|
|
461
|
+
* These modes serve as staging for applications that want to enable sweep by providing an incremental
|
|
462
|
+
* way to test and validate sweep works as expected.
|
|
463
|
+
*/
|
|
464
|
+
if (this.configs.testMode) {
|
|
465
|
+
// If we are running in GC test mode, unreferenced nodes (gcResult.deletedNodeIds) are deleted.
|
|
466
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
if (this.configs.tombstoneMode) {
|
|
470
|
+
this.tombstones = sweepReadyNodes;
|
|
471
|
+
// If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
|
|
472
|
+
// involving access to "deleted" data without actually deleting the data from summaries.
|
|
473
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
if (!this.configs.shouldRunSweep) {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
// 1. Call the runtime to delete sweep ready nodes. The runtime returns a list of nodes it deleted.
|
|
480
|
+
// TODO: GC:Validation - validate that removed routes are not double delete and that the child routes of
|
|
481
|
+
// removed routes are deleted as well.
|
|
482
|
+
const deletedNodeIds = this.runtime.deleteSweepReadyNodes(sweepReadyNodes);
|
|
483
|
+
// 2. Clear unreferenced state tracking for deleted nodes.
|
|
484
|
+
for (const nodeId of deletedNodeIds) {
|
|
590
485
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
591
486
|
// TODO: GC:Validation - assert that the nodeStateTracker is defined
|
|
592
487
|
if (nodeStateTracker !== undefined) {
|
|
@@ -598,23 +493,7 @@ export class GarbageCollector {
|
|
|
598
493
|
// TODO: GC:Validation - assert that the deleted node is not a duplicate
|
|
599
494
|
this.deletedNodes.add(nodeId);
|
|
600
495
|
}
|
|
601
|
-
return
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* @returns IGarbageCollectionData after deleting the sweptRoutes from the gcData
|
|
605
|
-
*/
|
|
606
|
-
deleteSweptRoutes(sweptRoutes, gcData) {
|
|
607
|
-
const sweptRoutesSet = new Set(sweptRoutes);
|
|
608
|
-
const gcNodes = {};
|
|
609
|
-
for (const [id, outboundRoutes] of Object.entries(gcData.gcNodes)) {
|
|
610
|
-
if (!sweptRoutesSet.has(id)) {
|
|
611
|
-
gcNodes[id] = Array.from(outboundRoutes);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
// TODO: GC:Validation - assert that the nodeId is in gcData
|
|
615
|
-
return {
|
|
616
|
-
gcNodes,
|
|
617
|
-
};
|
|
496
|
+
return deletedNodeIds;
|
|
618
497
|
}
|
|
619
498
|
/**
|
|
620
499
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
@@ -637,17 +516,11 @@ export class GarbageCollector {
|
|
|
637
516
|
if (previousGCData === undefined) {
|
|
638
517
|
return undefined;
|
|
639
518
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
eventName: "gcUnknownOutboundReferences",
|
|
646
|
-
gcNodeId: missingExplicitReference[0],
|
|
647
|
-
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
}
|
|
519
|
+
/**
|
|
520
|
+
* If there are references that were not explicitly notified to GC, log an error because this should never happen.
|
|
521
|
+
* If it does, this may result in the unreferenced timestamps of these nodes not updated when they were referenced.
|
|
522
|
+
*/
|
|
523
|
+
this.telemetryTracker.logIfMissingExplicitReferences(currentGCData, previousGCData, this.newReferencesSinceLastRun, logger);
|
|
651
524
|
// No references were added since the last run so we don't have to update reference states of any unreferenced
|
|
652
525
|
// nodes. There is no in between state at this point.
|
|
653
526
|
if (this.newReferencesSinceLastRun.size === 0) {
|
|
@@ -694,50 +567,119 @@ export class GarbageCollector {
|
|
|
694
567
|
return gcResult.referencedNodeIds;
|
|
695
568
|
}
|
|
696
569
|
/**
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
* In more simple terms:
|
|
702
|
-
* Missing Explicit References = Current References - Previous References - Explicitly Added References;
|
|
703
|
-
*
|
|
704
|
-
* @param currentGCData - The GC data (reference graph) from the current GC run.
|
|
705
|
-
* @param previousGCData - The GC data (reference graph) from the previous GC run.
|
|
706
|
-
* @param explicitReferences - New references added explicity between the previous and the current run.
|
|
707
|
-
* @returns - a list of missing explicit references
|
|
570
|
+
* Summarizes the GC data and returns it as a summary tree.
|
|
571
|
+
* We current write the entire GC state in a single blob. This can be modified later to write multiple
|
|
572
|
+
* blobs. All the blob keys should start with `gcBlobPrefix`.
|
|
708
573
|
*/
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
574
|
+
summarize(fullTree, trackState, telemetryContext) {
|
|
575
|
+
var _a;
|
|
576
|
+
if (!this.configs.shouldRunGC || this.gcDataFromLastRun === undefined) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const gcState = { gcNodes: {} };
|
|
580
|
+
for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
|
|
581
|
+
gcState.gcNodes[nodeId] = {
|
|
582
|
+
outboundRoutes,
|
|
583
|
+
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return this.summaryStateTracker.summarize(fullTree, trackState, gcState, this.deletedNodes, this.tombstones);
|
|
587
|
+
}
|
|
588
|
+
getMetadata() {
|
|
589
|
+
return {
|
|
718
590
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* 2. Only include data store and blob routes since GC only works for these two.
|
|
722
|
-
* Note: Due to a bug with de-duped blobs, only adding data store routes for now.
|
|
723
|
-
* 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
|
|
724
|
-
* explicit routes to them.
|
|
591
|
+
* If GC is enabled, the GC data is written using the GC version in effect and that is the gcFeature that goes
|
|
592
|
+
* into the metadata blob. If GC is disabled, the gcFeature is 0.
|
|
725
593
|
*/
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
594
|
+
gcFeature: this.configs.gcEnabled ? this.configs.gcVersionInEffect : 0,
|
|
595
|
+
gcFeatureMatrix: this.configs.persistedGcFeatureMatrix,
|
|
596
|
+
sessionExpiryTimeoutMs: this.configs.sessionExpiryTimeoutMs,
|
|
597
|
+
sweepEnabled: false,
|
|
598
|
+
sweepTimeoutMs: this.configs.sweepTimeoutMs,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
|
|
603
|
+
* is downloaded and should be used to update the state.
|
|
604
|
+
*/
|
|
605
|
+
async refreshLatestSummary(proposalHandle, result, readAndParseBlob) {
|
|
606
|
+
const latestSnapshotData = await this.summaryStateTracker.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
|
|
607
|
+
// If the latest summary was updated but it was not tracked by this client, our state needs to be updated from
|
|
608
|
+
// this snapshot data.
|
|
609
|
+
if (this.shouldRunGC && result.latestSummaryUpdated && !result.wasSummaryTracked) {
|
|
610
|
+
// The current reference timestamp should be available if we are refreshing state from a snapshot. There has
|
|
611
|
+
// to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
|
|
612
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
613
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
614
|
+
throw DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, {
|
|
615
|
+
proposalHandle,
|
|
616
|
+
summaryRefSeq: result.summaryRefSeq,
|
|
617
|
+
gcConfigs: JSON.stringify(this.configs),
|
|
618
|
+
});
|
|
737
619
|
}
|
|
620
|
+
this.updateStateFromSnapshotData(latestSnapshotData, currentReferenceTimestampMs);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
625
|
+
* @param nodePath - The id of the node that changed.
|
|
626
|
+
* @param reason - Whether the node was loaded or changed.
|
|
627
|
+
* @param timestampMs - The timestamp when the node changed.
|
|
628
|
+
* @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
|
|
629
|
+
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
630
|
+
*/
|
|
631
|
+
nodeUpdated(nodePath, reason, timestampMs, packagePath, requestHeaders) {
|
|
632
|
+
if (!this.configs.shouldRunGC) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
this.telemetryTracker.nodeUsed({
|
|
636
|
+
id: nodePath,
|
|
637
|
+
usageType: reason,
|
|
638
|
+
currentReferenceTimestampMs: timestampMs !== null && timestampMs !== void 0 ? timestampMs : this.runtime.getCurrentReferenceTimestampMs(),
|
|
639
|
+
packagePath,
|
|
640
|
+
completedGCRuns: this.completedRuns,
|
|
641
|
+
isTombstoned: this.tombstones.includes(nodePath),
|
|
642
|
+
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
643
|
+
viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle],
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Called when an outbound reference is added to a node. This is used to identify all nodes that have been
|
|
648
|
+
* referenced between summaries so that their unreferenced timestamp can be reset.
|
|
649
|
+
*
|
|
650
|
+
* @param fromNodePath - The node from which the reference is added.
|
|
651
|
+
* @param toNodePath - The node to which the reference is added.
|
|
652
|
+
*/
|
|
653
|
+
addedOutboundReference(fromNodePath, toNodePath) {
|
|
654
|
+
var _a;
|
|
655
|
+
if (!this.configs.shouldRunGC) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const outboundRoutes = (_a = this.newReferencesSinceLastRun.get(fromNodePath)) !== null && _a !== void 0 ? _a : [];
|
|
659
|
+
outboundRoutes.push(toNodePath);
|
|
660
|
+
this.newReferencesSinceLastRun.set(fromNodePath, outboundRoutes);
|
|
661
|
+
this.telemetryTracker.nodeUsed({
|
|
662
|
+
id: toNodePath,
|
|
663
|
+
usageType: "Revived",
|
|
664
|
+
currentReferenceTimestampMs: this.runtime.getCurrentReferenceTimestampMs(),
|
|
665
|
+
packagePath: undefined,
|
|
666
|
+
completedGCRuns: this.completedRuns,
|
|
667
|
+
isTombstoned: this.tombstones.includes(toNodePath),
|
|
668
|
+
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
669
|
+
fromId: fromNodePath,
|
|
738
670
|
});
|
|
739
|
-
|
|
740
|
-
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
|
|
674
|
+
* cases where objects are used after they are deleted and throw / log errors accordingly.
|
|
675
|
+
*/
|
|
676
|
+
isNodeDeleted(nodePath) {
|
|
677
|
+
return this.deletedNodes.has(nodePath);
|
|
678
|
+
}
|
|
679
|
+
dispose() {
|
|
680
|
+
var _a;
|
|
681
|
+
(_a = this.sessionExpiryTimer) === null || _a === void 0 ? void 0 : _a.clear();
|
|
682
|
+
this.sessionExpiryTimer = undefined;
|
|
741
683
|
}
|
|
742
684
|
/**
|
|
743
685
|
* Generates the stats of a garbage collection run from the given results of the run.
|
|
@@ -795,128 +737,5 @@ export class GarbageCollector {
|
|
|
795
737
|
}
|
|
796
738
|
return gcStats;
|
|
797
739
|
}
|
|
798
|
-
/**
|
|
799
|
-
* For nodes that are ready to sweep, log an event for now. Until we start running sweep which deletes objects,
|
|
800
|
-
* this will give us a view into how much deleted content a container has.
|
|
801
|
-
*/
|
|
802
|
-
logSweepEvents(logger, currentReferenceTimestampMs) {
|
|
803
|
-
if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
804
|
-
this.configs.sweepTimeoutMs === undefined) {
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
|
|
808
|
-
if (nodeStateTracker.state !== UnreferencedState.SweepReady) {
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
const nodeType = this.runtime.getNodeType(nodeId);
|
|
812
|
-
if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
// Log deleted event for each node only once to reduce noise in telemetry.
|
|
816
|
-
const uniqueEventId = `Deleted-${nodeId}`;
|
|
817
|
-
if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
821
|
-
logger.sendTelemetryEvent({
|
|
822
|
-
eventName: "GCObjectDeleted",
|
|
823
|
-
id: nodeId,
|
|
824
|
-
type: nodeType,
|
|
825
|
-
age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
|
|
826
|
-
timeout: this.configs.sweepTimeoutMs,
|
|
827
|
-
completedGCRuns: this.completedRuns,
|
|
828
|
-
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Called when an inactive node is used after. Queue up an event that will be logged next time GC runs.
|
|
834
|
-
*/
|
|
835
|
-
inactiveNodeUsed(usageType, nodeId, nodeStateTracker, fromNodeId, packagePath, currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs(), requestHeaders) {
|
|
836
|
-
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
837
|
-
// logging as nothing interesting would have happened worth logging.
|
|
838
|
-
// If the node is active, skip logging.
|
|
839
|
-
if (currentReferenceTimestampMs === undefined ||
|
|
840
|
-
nodeStateTracker.state === UnreferencedState.Active) {
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
// We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
|
|
844
|
-
// as unreferenced. Also, if an inactive DDS is used, the corresponding data store store will also be used.
|
|
845
|
-
const nodeType = this.runtime.getNodeType(nodeId);
|
|
846
|
-
if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
const state = nodeStateTracker.state;
|
|
850
|
-
const uniqueEventId = `${state}-${nodeId}-${usageType}`;
|
|
851
|
-
if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
855
|
-
const propsToLog = Object.assign(Object.assign({ id: nodeId, type: nodeType, unrefTime: nodeStateTracker.unreferencedTimestampMs, age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs, timeout: nodeStateTracker.state === UnreferencedState.Inactive
|
|
856
|
-
? this.configs.inactiveTimeoutMs
|
|
857
|
-
: this.configs.sweepTimeoutMs, completedGCRuns: this.completedRuns, lastSummaryTime: this.getLastSummaryTimestampMs() }, this.createContainerMetadata), { viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle], fromId: fromNodeId });
|
|
858
|
-
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
859
|
-
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
860
|
-
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
861
|
-
// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
|
|
862
|
-
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
863
|
-
if (this.isSummarizerClient) {
|
|
864
|
-
this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
868
|
-
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
869
|
-
// Events generated:
|
|
870
|
-
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
871
|
-
if (usageType === "Loaded") {
|
|
872
|
-
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() });
|
|
873
|
-
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
874
|
-
// detecting something wrong with GC either from the partner or from the runtime itself.
|
|
875
|
-
if (state === UnreferencedState.Inactive) {
|
|
876
|
-
this.mc.logger.sendTelemetryEvent(event);
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
this.mc.logger.sendErrorEvent(event);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
async logUnreferencedEvents(logger) {
|
|
885
|
-
// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
|
|
886
|
-
// summary time they are then logged.
|
|
887
|
-
// Events generated:
|
|
888
|
-
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
889
|
-
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
890
|
-
for (const eventProps of this.pendingEventsQueue) {
|
|
891
|
-
const { usageType, state } = eventProps, propsToLog = __rest(eventProps, ["usageType", "state"]);
|
|
892
|
-
/**
|
|
893
|
-
* Revived event is logged only if the node is active. If the node is not active, the reference to it was
|
|
894
|
-
* from another unreferenced node and this scenario is not interesting to log.
|
|
895
|
-
* Loaded and Changed events are logged only if the node is not active. If the node is active, it was
|
|
896
|
-
* revived and a Revived event will be logged for it.
|
|
897
|
-
*/
|
|
898
|
-
const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
|
|
899
|
-
const active = nodeStateTracker === undefined ||
|
|
900
|
-
nodeStateTracker.state === UnreferencedState.Active;
|
|
901
|
-
if ((usageType === "Revived") === active) {
|
|
902
|
-
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
903
|
-
const fromPkg = eventProps.fromId
|
|
904
|
-
? await this.getNodePackagePath(eventProps.fromId)
|
|
905
|
-
: undefined;
|
|
906
|
-
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg
|
|
907
|
-
? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
|
|
908
|
-
: undefined, fromPkg: fromPkg
|
|
909
|
-
? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
|
|
910
|
-
: undefined });
|
|
911
|
-
if (state === UnreferencedState.Inactive) {
|
|
912
|
-
logger.sendTelemetryEvent(event);
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
logger.sendErrorEvent(event);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
this.pendingEventsQueue = [];
|
|
920
|
-
}
|
|
921
740
|
}
|
|
922
741
|
//# sourceMappingURL=garbageCollection.js.map
|