@fluidframework/container-runtime 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.3.1.0.125672
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 +21 -10
- package/.mocharc.js +2 -2
- package/api-extractor.json +2 -2
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +2 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +53 -34
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +236 -124
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +11 -9
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerHandleContext.d.ts.map +1 -1
- package/dist/containerHandleContext.js +3 -1
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +95 -46
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +288 -135
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +11 -9
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +2 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +38 -21
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +7 -3
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStoreRegistry.d.ts.map +1 -1
- package/dist/dataStoreRegistry.js +3 -1
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/dataStores.d.ts +12 -9
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +68 -46
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +8 -3
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +50 -26
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +348 -196
- 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/garbageCollectionHelpers.d.ts +15 -0
- package/dist/garbageCollectionHelpers.d.ts.map +1 -0
- package/dist/garbageCollectionHelpers.js +27 -0
- package/dist/garbageCollectionHelpers.js.map +1 -0
- package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +14 -10
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +3 -4
- 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 +48 -7
- 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 +33 -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 +117 -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 +38 -27
- 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/opProperties.d.ts.map +1 -1
- package/dist/opProperties.js +1 -3
- package/dist/opProperties.js.map +1 -1
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +10 -4
- package/dist/orderedClientElection.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 +4 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +134 -161
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/dist/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +34 -22
- 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 +11 -21
- package/dist/scheduleManager.js.map +1 -1
- package/dist/serializedSnapshotStorage.d.ts.map +1 -1
- package/dist/serializedSnapshotStorage.js +3 -1
- package/dist/serializedSnapshotStorage.js.map +1 -1
- package/dist/summarizer.d.ts +2 -3
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +39 -18
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.d.ts +1 -2
- package/dist/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +3 -30
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerHandle.d.ts.map +1 -1
- package/dist/summarizerHandle.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +6 -9
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +22 -25
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +18 -8
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +18 -11
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +32 -14
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +21 -9
- package/dist/summaryManager.js.map +1 -1
- package/dist/throttler.d.ts +2 -2
- package/dist/throttler.d.ts.map +1 -1
- package/dist/throttler.js +4 -4
- package/dist/throttler.js.map +1 -1
- package/garbageCollection.md +15 -2
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +2 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +53 -34
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +239 -127
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +11 -9
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerHandleContext.d.ts.map +1 -1
- package/lib/containerHandleContext.js +3 -1
- package/lib/containerHandleContext.js.map +1 -1
- package/lib/containerRuntime.d.ts +95 -46
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +291 -138
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +11 -9
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +2 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +40 -23
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +7 -3
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStoreRegistry.d.ts.map +1 -1
- package/lib/dataStoreRegistry.js +3 -1
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/dataStores.d.ts +12 -9
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +74 -52
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +9 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +50 -26
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +347 -195
- 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/garbageCollectionHelpers.d.ts +15 -0
- package/lib/garbageCollectionHelpers.d.ts.map +1 -0
- package/lib/garbageCollectionHelpers.js +23 -0
- package/lib/garbageCollectionHelpers.js.map +1 -0
- package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +14 -10
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +3 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -3
- 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 +48 -7
- 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 +33 -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 +116 -5
- 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 +38 -27
- 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/opProperties.d.ts.map +1 -1
- package/lib/opProperties.js +1 -3
- package/lib/opProperties.js.map +1 -1
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +10 -4
- package/lib/orderedClientElection.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 +4 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +134 -161
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/lib/runWhileConnectedCoordinator.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +35 -23
- 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 +11 -21
- package/lib/scheduleManager.js.map +1 -1
- package/lib/serializedSnapshotStorage.d.ts.map +1 -1
- package/lib/serializedSnapshotStorage.js +3 -1
- package/lib/serializedSnapshotStorage.js.map +1 -1
- package/lib/summarizer.d.ts +2 -3
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +39 -18
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.d.ts +1 -2
- package/lib/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +3 -30
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerHandle.d.ts.map +1 -1
- package/lib/summarizerHandle.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +6 -9
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +22 -25
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +18 -8
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +20 -13
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +32 -14
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +21 -9
- package/lib/summaryManager.js.map +1 -1
- package/lib/throttler.d.ts +2 -2
- package/lib/throttler.d.ts.map +1 -1
- package/lib/throttler.js +4 -4
- package/lib/throttler.js.map +1 -1
- package/package.json +27 -24
- package/prettier.config.cjs +1 -1
- package/src/batchTracker.ts +55 -50
- package/src/blobManager.ts +799 -593
- package/src/connectionTelemetry.ts +280 -249
- package/src/containerHandleContext.ts +27 -29
- package/src/containerRuntime.ts +3123 -2793
- package/src/dataStore.ts +172 -159
- package/src/dataStoreContext.ts +1048 -991
- package/src/dataStoreContexts.ts +178 -161
- package/src/dataStoreRegistry.ts +25 -20
- package/src/dataStores.ts +784 -711
- package/src/deltaScheduler.ts +158 -150
- package/src/garbageCollection.ts +1795 -1546
- package/src/garbageCollectionConstants.ts +10 -7
- package/src/garbageCollectionHelpers.ts +37 -0
- package/src/gcSweepReadyUsageDetection.ts +89 -83
- package/src/index.ts +67 -69
- package/src/opLifecycle/batchManager.ts +148 -86
- package/src/opLifecycle/definitions.ts +45 -19
- package/src/opLifecycle/index.ts +6 -5
- package/src/opLifecycle/opCompressor.ts +57 -39
- package/src/opLifecycle/opDecompressor.ts +104 -64
- package/src/opLifecycle/opSplitter.ts +226 -66
- package/src/opLifecycle/outbox.ts +206 -182
- package/src/opLifecycle/remoteMessageProcessor.ts +63 -47
- package/src/opProperties.ts +11 -9
- package/src/orderedClientElection.ts +489 -457
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +379 -381
- package/src/runWhileConnectedCoordinator.ts +78 -71
- package/src/runningSummarizer.ts +619 -582
- package/src/scheduleManager.ts +299 -280
- package/src/serializedSnapshotStorage.ts +116 -111
- package/src/summarizer.ts +417 -381
- package/src/summarizerClientElection.ts +107 -129
- package/src/summarizerHandle.ts +11 -9
- package/src/summarizerHeuristics.ts +183 -186
- package/src/summarizerTypes.ts +344 -333
- package/src/summaryCollection.ts +378 -349
- package/src/summaryFormat.ts +146 -127
- package/src/summaryGenerator.ts +464 -406
- package/src/summaryManager.ts +377 -348
- package/src/throttler.ts +131 -122
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +9 -13
package/lib/garbageCollection.js
CHANGED
|
@@ -14,19 +14,18 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
14
14
|
return t;
|
|
15
15
|
};
|
|
16
16
|
import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
17
|
-
import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
|
|
18
|
-
import { cloneGCData,
|
|
17
|
+
import { ClientSessionExpiredError, DataProcessingError, UsageError, } from "@fluidframework/container-utils";
|
|
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, gcDeletedBlobKey, } 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 { sendGCUnexpectedUsageEvent } from "./garbageCollectionHelpers";
|
|
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,25 +138,13 @@ 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();
|
|
144
|
+
// A list of nodes that have been tombstoned.
|
|
160
145
|
this.tombstones = [];
|
|
146
|
+
// A list of nodes that have been deleted during sweep phase.
|
|
147
|
+
this.deletedNodes = new Set();
|
|
161
148
|
// Map of node ids to their unreferenced state tracker.
|
|
162
149
|
this.unreferencedNodesState = new Map();
|
|
163
150
|
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
@@ -177,7 +164,14 @@ export class GarbageCollector {
|
|
|
177
164
|
const baseSnapshot = createParams.baseSnapshot;
|
|
178
165
|
const metadata = createParams.metadata;
|
|
179
166
|
const readAndParseBlob = createParams.readAndParseBlob;
|
|
180
|
-
this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", {
|
|
167
|
+
this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", {
|
|
168
|
+
all: { completedGCRuns: () => this.completedRuns },
|
|
169
|
+
}));
|
|
170
|
+
// If version upgrade is not enabled, fall back to the stable GC version.
|
|
171
|
+
this.currentGCVersion =
|
|
172
|
+
this.mc.config.getBoolean(gcVersionUpgradeToV2Key) === true
|
|
173
|
+
? currentGCVersion
|
|
174
|
+
: stableGCVersion;
|
|
181
175
|
this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
182
176
|
let prevSummaryGCVersion;
|
|
183
177
|
/**
|
|
@@ -191,8 +185,8 @@ export class GarbageCollector {
|
|
|
191
185
|
function computeSweepTimeout(sessionExpiryTimeoutMs) {
|
|
192
186
|
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
193
187
|
const bufferMs = oneDayMs;
|
|
194
|
-
return sessionExpiryTimeoutMs &&
|
|
195
|
-
|
|
188
|
+
return (sessionExpiryTimeoutMs &&
|
|
189
|
+
sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
196
190
|
}
|
|
197
191
|
/**
|
|
198
192
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
@@ -223,11 +217,11 @@ export class GarbageCollector {
|
|
|
223
217
|
// flag in GC options to false.
|
|
224
218
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
225
219
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
226
|
-
|
|
227
|
-
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
220
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true;
|
|
228
221
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
229
222
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
230
|
-
this.sessionExpiryTimeoutMs =
|
|
223
|
+
this.sessionExpiryTimeoutMs =
|
|
224
|
+
(_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
|
|
231
225
|
}
|
|
232
226
|
this.sweepTimeoutMs =
|
|
233
227
|
testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
@@ -237,7 +231,9 @@ export class GarbageCollector {
|
|
|
237
231
|
// If Test Override config is set, override Session Expiry timeout.
|
|
238
232
|
const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
239
233
|
const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
|
|
240
|
-
this.sessionExpiryTimer = new Timer(timeoutMs, () => {
|
|
234
|
+
this.sessionExpiryTimer = new Timer(timeoutMs, () => {
|
|
235
|
+
this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
|
|
236
|
+
});
|
|
241
237
|
this.sessionExpiryTimer.start();
|
|
242
238
|
}
|
|
243
239
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -252,11 +248,12 @@ export class GarbageCollector {
|
|
|
252
248
|
*
|
|
253
249
|
* These conditions can be overridden via runGCKey feature flag.
|
|
254
250
|
*/
|
|
255
|
-
this.shouldRunGC =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
this.shouldRunGC =
|
|
252
|
+
(_d = this.mc.config.getBoolean(runGCKey)) !== null && _d !== void 0 ? _d :
|
|
253
|
+
// GC must be enabled for the document.
|
|
254
|
+
(this.gcEnabled &&
|
|
255
|
+
// GC must not be disabled via GC options.
|
|
256
|
+
!this.gcOptions.disableGC);
|
|
260
257
|
/**
|
|
261
258
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
262
259
|
*
|
|
@@ -268,24 +265,26 @@ export class GarbageCollector {
|
|
|
268
265
|
* feature flag.
|
|
269
266
|
*/
|
|
270
267
|
this.shouldRunSweep =
|
|
271
|
-
this.shouldRunGC
|
|
272
|
-
|
|
273
|
-
|
|
268
|
+
this.shouldRunGC &&
|
|
269
|
+
this.sweepTimeoutMs !== undefined &&
|
|
270
|
+
((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
|
|
274
271
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
275
272
|
// Override inactive timeout if test config or gc options to override it is set.
|
|
276
|
-
this.inactiveTimeoutMs =
|
|
273
|
+
this.inactiveTimeoutMs =
|
|
274
|
+
(_g = (_f = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _f !== void 0 ? _f : this.gcOptions.inactiveTimeoutMs) !== null && _g !== void 0 ? _g : defaultInactiveTimeoutMs;
|
|
277
275
|
// Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
|
|
278
276
|
if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
|
|
279
277
|
throw new UsageError("inactive timeout should not be greater than the sweep timeout");
|
|
280
278
|
}
|
|
281
279
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
282
|
-
this.testMode =
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
280
|
+
this.testMode =
|
|
281
|
+
(_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
|
|
282
|
+
// Whether we are running in tombstone mode. This is enabled by default if sweep won't run. It can be disabled
|
|
283
|
+
// via feature flags.
|
|
284
|
+
this.tombstoneMode =
|
|
285
|
+
!this.shouldRunSweep && this.mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
286
|
+
// If GC ran in the container that generated the base snapshot, it will have a GC tree.
|
|
287
|
+
this.wasGCRunInLatestSummary = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
|
|
289
288
|
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
290
289
|
// it involves fetching blobs from storage which is expensive.
|
|
291
290
|
this.baseSnapshotDataP = new LazyPromise(async () => {
|
|
@@ -302,11 +301,13 @@ export class GarbageCollector {
|
|
|
302
301
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
303
302
|
// consolidate into IGarbageCollectionState format.
|
|
304
303
|
// Add a node for the root node that is not present in older snapshot format.
|
|
305
|
-
const gcState = {
|
|
304
|
+
const gcState = {
|
|
305
|
+
gcNodes: { "/": { outboundRoutes: [] } },
|
|
306
|
+
};
|
|
306
307
|
const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
|
|
307
308
|
assert(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
308
309
|
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
309
|
-
const blobId = dsSnapshotTree.blobs[
|
|
310
|
+
const blobId = dsSnapshotTree.blobs[gcTreeKey];
|
|
310
311
|
if (blobId === undefined) {
|
|
311
312
|
continue;
|
|
312
313
|
}
|
|
@@ -326,14 +327,19 @@ export class GarbageCollector {
|
|
|
326
327
|
// Prefix the data store id to the GC node ids to make them relative to the root from being
|
|
327
328
|
// relative to the data store. Similar to how its done in DataStore::getGCData.
|
|
328
329
|
const rootId = id === "/" ? dsRootId : `${dsRootId}${id}`;
|
|
329
|
-
gcState.gcNodes[rootId] = {
|
|
330
|
+
gcState.gcNodes[rootId] = {
|
|
331
|
+
outboundRoutes: Array.from(outboundRoutes),
|
|
332
|
+
};
|
|
330
333
|
}
|
|
331
334
|
assert(gcState.gcNodes[dsRootId] !== undefined, 0x2a9 /* GC nodes for data store not in GC blob */);
|
|
332
|
-
gcState.gcNodes[dsRootId].unreferencedTimestampMs =
|
|
335
|
+
gcState.gcNodes[dsRootId].unreferencedTimestampMs =
|
|
336
|
+
gcSummaryDetails.unrefTimestamp;
|
|
333
337
|
}
|
|
334
338
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
335
339
|
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
336
|
-
return Object.keys(gcState.gcNodes).length === 1
|
|
340
|
+
return Object.keys(gcState.gcNodes).length === 1
|
|
341
|
+
? undefined
|
|
342
|
+
: { gcState, tombstones: undefined, deletedNodes: undefined };
|
|
337
343
|
}
|
|
338
344
|
catch (error) {
|
|
339
345
|
const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
@@ -347,7 +353,6 @@ export class GarbageCollector {
|
|
|
347
353
|
* GC state and updates their inactive or sweep ready state.
|
|
348
354
|
*/
|
|
349
355
|
this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
|
|
350
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
351
356
|
/**
|
|
352
357
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
353
358
|
* how long objects have been unreferenced and if they can be deleted.
|
|
@@ -356,6 +361,7 @@ export class GarbageCollector {
|
|
|
356
361
|
* for this container and it is in read mode. In this scenario, there is no point in running GC anyway
|
|
357
362
|
* because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
358
363
|
*/
|
|
364
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
359
365
|
if (currentReferenceTimestampMs === undefined) {
|
|
360
366
|
// Log an event so we can evaluate how often we run into this scenario.
|
|
361
367
|
this.mc.logger.sendErrorEvent({
|
|
@@ -364,38 +370,24 @@ export class GarbageCollector {
|
|
|
364
370
|
});
|
|
365
371
|
return;
|
|
366
372
|
}
|
|
367
|
-
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
368
373
|
/**
|
|
369
374
|
* The base snapshot data will not be present if the container is loaded from:
|
|
370
375
|
* 1. The first summary created by the detached container.
|
|
371
376
|
* 2. A summary that was generated with GC disabled.
|
|
372
377
|
* 3. A summary that was generated before GC even existed.
|
|
373
378
|
*/
|
|
379
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
374
380
|
if (baseSnapshotData === undefined) {
|
|
375
381
|
return;
|
|
376
382
|
}
|
|
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
|
-
}
|
|
383
|
+
this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
|
|
392
384
|
});
|
|
393
|
-
// Get the GC details
|
|
394
|
-
//
|
|
385
|
+
// Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
|
|
386
|
+
// used to initialize the GC state of all the nodes in the container.
|
|
395
387
|
this.baseGCDetailsP = new LazyPromise(async () => {
|
|
396
388
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
397
389
|
if (baseSnapshotData === undefined) {
|
|
398
|
-
return
|
|
390
|
+
return {};
|
|
399
391
|
}
|
|
400
392
|
const gcNodes = {};
|
|
401
393
|
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
@@ -405,18 +397,7 @@ export class GarbageCollector {
|
|
|
405
397
|
// This is an optimization for space (vs performance) wherein we don't need to store the used routes of
|
|
406
398
|
// each node in the summary.
|
|
407
399
|
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;
|
|
400
|
+
return { gcData: { gcNodes }, usedRoutes };
|
|
420
401
|
});
|
|
421
402
|
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
422
403
|
// summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
|
|
@@ -437,39 +418,143 @@ export class GarbageCollector {
|
|
|
437
418
|
*
|
|
438
419
|
* 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
|
|
439
420
|
*
|
|
440
|
-
* 3.
|
|
421
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
422
|
+
*
|
|
423
|
+
* 4. The GC version in the latest summary is different from the current GC version. This can happen if:
|
|
441
424
|
*
|
|
442
|
-
*
|
|
425
|
+
* 4.1. The summary this client loaded with has data from a different GC version.
|
|
443
426
|
*
|
|
444
|
-
*
|
|
427
|
+
* 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
445
428
|
*/
|
|
446
429
|
get summaryStateNeedsReset() {
|
|
447
|
-
return this.
|
|
448
|
-
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
|
|
430
|
+
return (this.gcStateNeedsReset ||
|
|
431
|
+
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion));
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Tells whether the GC state needs to be reset. This can happen under 3 conditions:
|
|
435
|
+
*
|
|
436
|
+
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
437
|
+
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
438
|
+
*
|
|
439
|
+
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
440
|
+
* a document and the first time GC is enabled after is was disabled before.
|
|
441
|
+
*
|
|
442
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
443
|
+
*
|
|
444
|
+
* Note that the state will be reset only once for the first summary generated after this returns true. After that,
|
|
445
|
+
* this will return false.
|
|
446
|
+
*/
|
|
447
|
+
get gcStateNeedsReset() {
|
|
448
|
+
return this.wasGCRunInLatestSummary !== this.shouldRunGC;
|
|
449
449
|
}
|
|
450
450
|
/** Returns a list of all the configurations for garbage collection. */
|
|
451
451
|
get configs() {
|
|
452
452
|
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);
|
|
453
453
|
}
|
|
454
454
|
/**
|
|
455
|
-
* Called during container initialization. Initialize the tombstone state
|
|
456
|
-
*
|
|
457
|
-
* in use or not.
|
|
455
|
+
* Called during container initialization. Initialize from the tombstone state in the base snapshot. This is done
|
|
456
|
+
* during initialization so that deleted or tombstoned objects are marked as such before they are loaded or used.
|
|
458
457
|
*/
|
|
459
458
|
async initializeBaseState() {
|
|
460
459
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
461
460
|
/**
|
|
462
|
-
* The base snapshot data
|
|
461
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
463
462
|
* 1. The first summary created by the detached container.
|
|
464
463
|
* 2. A summary that was generated with GC disabled.
|
|
465
464
|
* 3. A summary that was generated before GC even existed.
|
|
466
|
-
* 4. A summary that was generated with tombstone feature disabled.
|
|
467
465
|
*/
|
|
468
|
-
if (
|
|
466
|
+
if (baseSnapshotData === undefined) {
|
|
469
467
|
return;
|
|
470
468
|
}
|
|
471
|
-
|
|
472
|
-
|
|
469
|
+
// Initialize the deleted nodes from the snapshot. This is done irrespective of whether sweep is enabled or not
|
|
470
|
+
// to identify deleted nodes' usage.
|
|
471
|
+
if (baseSnapshotData.deletedNodes !== undefined) {
|
|
472
|
+
this.deletedNodes = new Set(baseSnapshotData.deletedNodes);
|
|
473
|
+
}
|
|
474
|
+
// If running in tombstone mode, initialize the tombstone state from the snapshot. Also, notify the runtime of
|
|
475
|
+
// tombstone routes.
|
|
476
|
+
if (this.tombstoneMode && baseSnapshotData.tombstones !== undefined) {
|
|
477
|
+
this.tombstones = Array.from(baseSnapshotData.tombstones);
|
|
478
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
|
|
483
|
+
* All current tracking is reset and updated from the data in the snapshot.
|
|
484
|
+
* @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
|
|
485
|
+
* is reset.
|
|
486
|
+
* @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
|
|
487
|
+
* timestamp.
|
|
488
|
+
*/
|
|
489
|
+
updateStateFromSnapshotData(snapshotData, currentReferenceTimestampMs) {
|
|
490
|
+
/**
|
|
491
|
+
* Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
|
|
492
|
+
* snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
|
|
493
|
+
* its refreshing state from a summary that happened at seq#900. In this case, there may be references between
|
|
494
|
+
* seq#901 and seq#1000 that we don't want to reset.
|
|
495
|
+
* Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
|
|
496
|
+
* references here. This should be fine because, in the worst case, we may end up updating the unreferenced
|
|
497
|
+
* timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
|
|
498
|
+
* scenarios, so it should be okay.
|
|
499
|
+
*/
|
|
500
|
+
// Clear all existing unreferenced state tracking.
|
|
501
|
+
for (const [, nodeStateTracker] of this.unreferencedNodesState) {
|
|
502
|
+
nodeStateTracker.stopTracking();
|
|
503
|
+
}
|
|
504
|
+
this.unreferencedNodesState.clear();
|
|
505
|
+
// If running sweep, the tombstone state represents the list of nodes that have been deleted during sweep.
|
|
506
|
+
// If running in tombstone mode, the tombstone state represents the list of nodes that have been marked as
|
|
507
|
+
// tombstones.
|
|
508
|
+
// If this call is because we are refreshing from a snapshot due to an ack, it is likely that the GC state
|
|
509
|
+
// in the snapshot is newer than this client's. And so, the deleted / tombstone nodes need to be updated.
|
|
510
|
+
if (this.shouldRunSweep) {
|
|
511
|
+
const snapshotDeletedNodes = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones)
|
|
512
|
+
? new Set(snapshotData.tombstones)
|
|
513
|
+
: undefined;
|
|
514
|
+
// If the snapshot contains deleted nodes that are not yet deleted by this client, ask the runtime to
|
|
515
|
+
// delete them.
|
|
516
|
+
if (snapshotDeletedNodes !== undefined) {
|
|
517
|
+
const newDeletedNodes = [];
|
|
518
|
+
for (const nodeId of snapshotDeletedNodes) {
|
|
519
|
+
if (!this.deletedNodes.has(nodeId)) {
|
|
520
|
+
newDeletedNodes.push(nodeId);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (newDeletedNodes.length > 0) {
|
|
524
|
+
// Call container runtime to delete these nodes and add deleted nodes to this.deletedNodes.
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
else if (this.tombstoneMode) {
|
|
529
|
+
// The snapshot may contain more or fewer tombstone nodes than this client. Update tombstone state and
|
|
530
|
+
// notify the runtime to update its state as well.
|
|
531
|
+
this.tombstones = (snapshotData === null || snapshotData === void 0 ? void 0 : snapshotData.tombstones) ? Array.from(snapshotData.tombstones) : [];
|
|
532
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
533
|
+
}
|
|
534
|
+
// If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
|
|
535
|
+
if (snapshotData === undefined) {
|
|
536
|
+
this.gcDataFromLastRun = undefined;
|
|
537
|
+
this.latestSummaryData = undefined;
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
// Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
|
|
541
|
+
// to the GC data from the snapshot data.
|
|
542
|
+
const gcNodes = {};
|
|
543
|
+
for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
|
|
544
|
+
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
545
|
+
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
546
|
+
}
|
|
547
|
+
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
548
|
+
}
|
|
549
|
+
this.gcDataFromLastRun = { gcNodes };
|
|
550
|
+
// If tracking state across summaries, update latest summary data from the snapshot's GC data.
|
|
551
|
+
if (this.trackGCState) {
|
|
552
|
+
this.latestSummaryData = {
|
|
553
|
+
serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
|
|
554
|
+
serializedTombstones: JSON.stringify(snapshotData.tombstones),
|
|
555
|
+
serializedDeletedNodes: JSON.stringify(snapshotData.deletedNodes),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
473
558
|
}
|
|
474
559
|
/**
|
|
475
560
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -503,7 +588,9 @@ export class GarbageCollector {
|
|
|
503
588
|
var _a;
|
|
504
589
|
const fullGC = (_a = options.fullGC) !== null && _a !== void 0 ? _a : (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
|
|
505
590
|
const logger = options.logger
|
|
506
|
-
? ChildLogger.create(options.logger, undefined, {
|
|
591
|
+
? ChildLogger.create(options.logger, undefined, {
|
|
592
|
+
all: { completedGCRuns: () => this.completedRuns },
|
|
593
|
+
})
|
|
507
594
|
: this.mc.logger;
|
|
508
595
|
/**
|
|
509
596
|
* If there is no current reference timestamp, skip running GC. We need the current timestamp to track
|
|
@@ -555,13 +642,13 @@ export class GarbageCollector {
|
|
|
555
642
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
556
643
|
// involving access to deleted data.
|
|
557
644
|
if (this.testMode) {
|
|
558
|
-
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds
|
|
645
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
559
646
|
}
|
|
560
647
|
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.
|
|
648
|
+
// If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
|
|
649
|
+
// involving access to "deleted" data without actually deleting the data from summaries.
|
|
650
|
+
// Note: we will not tombstone in test mode.
|
|
651
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
565
652
|
}
|
|
566
653
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
567
654
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
@@ -576,30 +663,44 @@ export class GarbageCollector {
|
|
|
576
663
|
*/
|
|
577
664
|
summarize(fullTree, trackState, telemetryContext) {
|
|
578
665
|
var _a;
|
|
579
|
-
if (!this.shouldRunGC || this.
|
|
666
|
+
if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
|
|
580
667
|
return;
|
|
581
668
|
}
|
|
582
669
|
const gcState = { gcNodes: {} };
|
|
583
|
-
for (const [nodeId, outboundRoutes] of Object.entries(this.
|
|
670
|
+
for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
|
|
584
671
|
gcState.gcNodes[nodeId] = {
|
|
585
672
|
outboundRoutes,
|
|
586
673
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
587
674
|
};
|
|
588
675
|
}
|
|
589
676
|
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
677
|
+
// Serialize and write deleted nodes, if any. This is done irrespective of whether sweep is enabled or not so
|
|
678
|
+
// to identify deleted nodes' usage.
|
|
679
|
+
const serializedDeletedNodes = this.deletedNodes.size > 0
|
|
680
|
+
? JSON.stringify(Array.from(this.deletedNodes).sort())
|
|
681
|
+
: undefined;
|
|
682
|
+
// If running in tombstone mode, serialize and write tombstones, if any.
|
|
590
683
|
const serializedTombstones = this.tombstoneMode
|
|
591
|
-
?
|
|
684
|
+
? this.tombstones.length > 0
|
|
685
|
+
? JSON.stringify(this.tombstones.sort())
|
|
686
|
+
: undefined
|
|
592
687
|
: undefined;
|
|
593
688
|
/**
|
|
594
|
-
* Incremental summary of GC data - If
|
|
595
|
-
*
|
|
689
|
+
* Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
|
|
690
|
+
* write summary handle instead of summary tree for GC.
|
|
691
|
+
* Otherwise, write the GC summary tree. In the tree, for each of these that changed, write a summary blob and
|
|
692
|
+
* for each of these that did not change, write a summary handle.
|
|
596
693
|
*/
|
|
597
694
|
if (this.trackGCState) {
|
|
598
|
-
this.pendingSummaryData = {
|
|
695
|
+
this.pendingSummaryData = {
|
|
696
|
+
serializedGCState,
|
|
697
|
+
serializedTombstones,
|
|
698
|
+
serializedDeletedNodes,
|
|
699
|
+
};
|
|
599
700
|
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
600
|
-
// If
|
|
601
|
-
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
602
|
-
|
|
701
|
+
// If nothing changed since last summary, send a summary handle for the entire GC data.
|
|
702
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState &&
|
|
703
|
+
this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
603
704
|
const stats = mergeStats();
|
|
604
705
|
stats.handleNodeCount++;
|
|
605
706
|
return {
|
|
@@ -611,24 +712,25 @@ export class GarbageCollector {
|
|
|
611
712
|
stats,
|
|
612
713
|
};
|
|
613
714
|
}
|
|
614
|
-
// If
|
|
615
|
-
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
715
|
+
// If some state changed, build a GC summary tree.
|
|
716
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, true /* trackState */);
|
|
616
717
|
}
|
|
617
718
|
}
|
|
618
719
|
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
619
|
-
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
720
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
|
|
620
721
|
}
|
|
621
722
|
/**
|
|
622
|
-
* Builds the GC summary tree which contains GC state and
|
|
623
|
-
* If trackState is false,
|
|
624
|
-
* If trackState is true,
|
|
723
|
+
* Builds the GC summary tree which contains GC state, deleted nodes and tombstones.
|
|
724
|
+
* If trackState is false, all of GC state, deleted nodes and tombstones are written as summary blobs.
|
|
725
|
+
* If trackState is true, only states that changed are written. Rest are written as handles.
|
|
625
726
|
* @param serializedGCState - The GC state serialized as string.
|
|
626
|
-
* @param serializedTombstones -
|
|
727
|
+
* @param serializedTombstones - The tombstone state serialized as string.
|
|
728
|
+
* @param serializedDeletedNodes - Deleted nodes serialized as string.
|
|
627
729
|
* @param trackState - Whether we are tracking GC state across summaries.
|
|
628
730
|
* @returns the GC summary tree.
|
|
629
731
|
*/
|
|
630
|
-
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
631
|
-
var _a, _b;
|
|
732
|
+
buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
|
|
733
|
+
var _a, _b, _c;
|
|
632
734
|
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
633
735
|
const builder = new SummaryTreeBuilder();
|
|
634
736
|
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
@@ -638,16 +740,28 @@ export class GarbageCollector {
|
|
|
638
740
|
else {
|
|
639
741
|
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
640
742
|
}
|
|
641
|
-
// If
|
|
642
|
-
|
|
743
|
+
// If tombstones exist, write a summary handle if it hasn't changed. If it has changed, write a
|
|
744
|
+
// summary blob.
|
|
745
|
+
if (serializedTombstones !== undefined) {
|
|
746
|
+
if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones &&
|
|
747
|
+
trackState) {
|
|
748
|
+
builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// If there are no deleted nodes, return the summary tree.
|
|
755
|
+
if (serializedDeletedNodes === undefined) {
|
|
643
756
|
return builder.getSummaryTree();
|
|
644
757
|
}
|
|
645
|
-
// If the
|
|
646
|
-
if (((
|
|
647
|
-
|
|
758
|
+
// If the deleted nodes hasn't changed, write a summary handle, else write a summary blob for it.
|
|
759
|
+
if (((_c = this.latestSummaryData) === null || _c === void 0 ? void 0 : _c.serializedDeletedNodes) === serializedDeletedNodes &&
|
|
760
|
+
trackState) {
|
|
761
|
+
builder.addHandle(gcDeletedBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcDeletedBlobKey}`);
|
|
648
762
|
}
|
|
649
763
|
else {
|
|
650
|
-
builder.addBlob(
|
|
764
|
+
builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
|
|
651
765
|
}
|
|
652
766
|
return builder.getSummaryTree();
|
|
653
767
|
}
|
|
@@ -664,50 +778,58 @@ export class GarbageCollector {
|
|
|
664
778
|
};
|
|
665
779
|
}
|
|
666
780
|
/**
|
|
667
|
-
* Returns a
|
|
668
|
-
*
|
|
781
|
+
* Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
|
|
782
|
+
* in the container.
|
|
669
783
|
*/
|
|
670
784
|
async getBaseGCDetails() {
|
|
671
785
|
return this.baseGCDetailsP;
|
|
672
786
|
}
|
|
673
787
|
/**
|
|
674
|
-
* Called
|
|
675
|
-
*
|
|
788
|
+
* Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
|
|
789
|
+
* is downloaded and should be used to update the state.
|
|
676
790
|
*/
|
|
677
|
-
async
|
|
678
|
-
|
|
791
|
+
async refreshLatestSummary(result, proposalHandle, summaryRefSeq, readAndParseBlob) {
|
|
792
|
+
// If the latest summary was updated and the summary was tracked, this client is the one that generated this
|
|
793
|
+
// summary. So, update wasGCRunInLatestSummary.
|
|
794
|
+
// Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
|
|
795
|
+
// true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
|
|
796
|
+
if (result.latestSummaryUpdated && result.wasSummaryTracked) {
|
|
797
|
+
this.wasGCRunInLatestSummary = this.shouldRunGC;
|
|
798
|
+
}
|
|
799
|
+
if (!result.latestSummaryUpdated || !this.shouldRunGC) {
|
|
679
800
|
return;
|
|
680
801
|
}
|
|
681
802
|
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
682
|
-
//
|
|
803
|
+
// Update latest state from pending.
|
|
683
804
|
if (result.wasSummaryTracked) {
|
|
684
805
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
685
|
-
this.initialStateNeedsReset = false;
|
|
686
806
|
if (this.trackGCState) {
|
|
687
807
|
this.latestSummaryData = this.pendingSummaryData;
|
|
688
808
|
this.pendingSummaryData = undefined;
|
|
689
809
|
}
|
|
690
810
|
return;
|
|
691
811
|
}
|
|
692
|
-
// If the summary was not tracked by this client,
|
|
693
|
-
// result as that is now the latest summary.
|
|
812
|
+
// If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
|
|
694
813
|
const snapshot = result.snapshot;
|
|
695
814
|
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
696
815
|
if (metadataBlobId) {
|
|
697
816
|
const metadata = await readAndParseBlob(metadataBlobId);
|
|
698
817
|
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
699
818
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
706
|
-
};
|
|
819
|
+
// The current reference timestamp should be available if we are refreshing state from a snapshot. There has
|
|
820
|
+
// to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
|
|
821
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
822
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
823
|
+
throw DataProcessingError.create("No reference timestamp when updating GC state from snapshot", "refreshLatestSummary", undefined, { proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) });
|
|
707
824
|
}
|
|
708
|
-
|
|
709
|
-
|
|
825
|
+
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
826
|
+
// If GC ran in the container that generated this snapshot, it will have a GC tree.
|
|
827
|
+
this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
|
|
828
|
+
let latestGCData;
|
|
829
|
+
if (gcSnapshotTree !== undefined) {
|
|
830
|
+
latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
710
831
|
}
|
|
832
|
+
this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
|
|
711
833
|
this.pendingSummaryData = undefined;
|
|
712
834
|
}
|
|
713
835
|
/**
|
|
@@ -746,6 +868,30 @@ export class GarbageCollector {
|
|
|
746
868
|
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
747
869
|
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
748
870
|
}
|
|
871
|
+
if (this.tombstones.includes(toNodePath)) {
|
|
872
|
+
const nodeType = this.runtime.getNodeType(toNodePath);
|
|
873
|
+
let eventName = "GC_Tombstone_SubDatastore_Revived";
|
|
874
|
+
if (nodeType === GCNodeType.DataStore) {
|
|
875
|
+
eventName = "GC_Tombstone_Datastore_Revived";
|
|
876
|
+
}
|
|
877
|
+
else if (nodeType === GCNodeType.Blob) {
|
|
878
|
+
eventName = "GC_Tombstone_Blob_Revived";
|
|
879
|
+
}
|
|
880
|
+
sendGCUnexpectedUsageEvent(this.mc, {
|
|
881
|
+
eventName,
|
|
882
|
+
category: "generic",
|
|
883
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
884
|
+
url: trimLeadingSlashes(toNodePath),
|
|
885
|
+
nodeType,
|
|
886
|
+
}, undefined /* packagePath */);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Returns whether a node with the given path has been deleted or not. This can be used by the runtime to identify
|
|
891
|
+
* cases where objects are used after they are deleted and throw / log errors accordingly.
|
|
892
|
+
*/
|
|
893
|
+
isNodeDeleted(nodePath) {
|
|
894
|
+
return this.deletedNodes.has(nodePath);
|
|
749
895
|
}
|
|
750
896
|
dispose() {
|
|
751
897
|
var _a;
|
|
@@ -762,7 +908,7 @@ export class GarbageCollector {
|
|
|
762
908
|
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
763
909
|
*/
|
|
764
910
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
765
|
-
this.
|
|
911
|
+
this.gcDataFromLastRun = cloneGCData(gcData);
|
|
766
912
|
this.tombstones = [];
|
|
767
913
|
this.newReferencesSinceLastRun.clear();
|
|
768
914
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -811,19 +957,18 @@ export class GarbageCollector {
|
|
|
811
957
|
*/
|
|
812
958
|
updateStateSinceLastRun(currentGCData, logger) {
|
|
813
959
|
// If we haven't run GC before there is nothing to do.
|
|
814
|
-
if (this.
|
|
960
|
+
if (this.gcDataFromLastRun === undefined) {
|
|
815
961
|
return;
|
|
816
962
|
}
|
|
817
963
|
// Find any references that haven't been identified correctly.
|
|
818
|
-
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.
|
|
964
|
+
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
|
|
819
965
|
if (missingExplicitReferences.length > 0) {
|
|
820
966
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
821
|
-
|
|
967
|
+
logger.sendErrorEvent({
|
|
822
968
|
eventName: "gcUnknownOutboundReferences",
|
|
823
969
|
gcNodeId: missingExplicitReference[0],
|
|
824
970
|
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
825
|
-
};
|
|
826
|
-
logger.sendPerformanceEvent(event);
|
|
971
|
+
});
|
|
827
972
|
});
|
|
828
973
|
}
|
|
829
974
|
// No references were added since the last run so we don't have to update reference states of any unreferenced
|
|
@@ -847,7 +992,7 @@ export class GarbageCollector {
|
|
|
847
992
|
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
848
993
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
849
994
|
*/
|
|
850
|
-
const gcDataSuperSet = concatGarbageCollectionData(this.
|
|
995
|
+
const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
|
|
851
996
|
const newOutboundRoutesSinceLastRun = [];
|
|
852
997
|
this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
|
|
853
998
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
@@ -865,7 +1010,10 @@ export class GarbageCollector {
|
|
|
865
1010
|
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
866
1011
|
* unreferenced and add unreferenced state.
|
|
867
1012
|
*/
|
|
868
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
|
|
1013
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
|
|
1014
|
+
"/",
|
|
1015
|
+
...newOutboundRoutesSinceLastRun,
|
|
1016
|
+
]);
|
|
869
1017
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
870
1018
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
871
1019
|
if (nodeStateTracker !== undefined) {
|
|
@@ -890,7 +1038,7 @@ export class GarbageCollector {
|
|
|
890
1038
|
* @returns - a list of missing explicit references
|
|
891
1039
|
*/
|
|
892
1040
|
findMissingExplicitReferences(currentGCData, previousGCData, explicitReferences) {
|
|
893
|
-
assert(previousGCData !== undefined, 0x2b7);
|
|
1041
|
+
assert(previousGCData !== undefined, 0x2b7 /* "Can't validate correctness without GC data from last run" */);
|
|
894
1042
|
const currentGraph = Object.entries(currentGCData.gcNodes);
|
|
895
1043
|
const missingExplicitReferences = [];
|
|
896
1044
|
currentGraph.forEach(([nodeId, currentOutboundRoutes]) => {
|
|
@@ -898,14 +1046,20 @@ export class GarbageCollector {
|
|
|
898
1046
|
const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
|
|
899
1047
|
const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
|
|
900
1048
|
const missingExplicitRoutes = [];
|
|
1049
|
+
/**
|
|
1050
|
+
* 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
|
|
1051
|
+
* explicit references should be added to missing explicit routes list.
|
|
1052
|
+
* 2. Only include data store and blob routes since GC only works for these two.
|
|
1053
|
+
* Note: Due to a bug with de-duped blobs, only adding data store routes for now.
|
|
1054
|
+
* 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
|
|
1055
|
+
* explicit routes to them.
|
|
1056
|
+
*/
|
|
901
1057
|
currentOutboundRoutes.forEach((route) => {
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
notRouteFromDDSToParentDataStore &&
|
|
908
|
-
(!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
|
|
1058
|
+
const nodeType = this.runtime.getNodeType(route);
|
|
1059
|
+
if ((nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&
|
|
1060
|
+
!nodeId.startsWith(route) &&
|
|
1061
|
+
!previousRoutes.includes(route) &&
|
|
1062
|
+
!explicitRoutes.includes(route)) {
|
|
909
1063
|
missingExplicitRoutes.push(route);
|
|
910
1064
|
}
|
|
911
1065
|
});
|
|
@@ -937,7 +1091,7 @@ export class GarbageCollector {
|
|
|
937
1091
|
gcStats.nodeCount++;
|
|
938
1092
|
// If there is no previous GC data, every node's state is generated and is considered as updated.
|
|
939
1093
|
// Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
|
|
940
|
-
const stateUpdated = this.
|
|
1094
|
+
const stateUpdated = this.gcDataFromLastRun === undefined ||
|
|
941
1095
|
this.unreferencedNodesState.has(nodeId) === referenced;
|
|
942
1096
|
if (stateUpdated) {
|
|
943
1097
|
gcStats.updatedNodeCount++;
|
|
@@ -977,7 +1131,8 @@ export class GarbageCollector {
|
|
|
977
1131
|
* this will give us a view into how much deleted content a container has.
|
|
978
1132
|
*/
|
|
979
1133
|
logSweepEvents(logger, currentReferenceTimestampMs) {
|
|
980
|
-
if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
1134
|
+
if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
1135
|
+
this.sweepTimeoutMs === undefined) {
|
|
981
1136
|
return;
|
|
982
1137
|
}
|
|
983
1138
|
this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
|
|
@@ -1012,7 +1167,8 @@ export class GarbageCollector {
|
|
|
1012
1167
|
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
1013
1168
|
// logging as nothing interesting would have happened worth logging.
|
|
1014
1169
|
// If the node is active, skip logging.
|
|
1015
|
-
if (currentReferenceTimestampMs === undefined ||
|
|
1170
|
+
if (currentReferenceTimestampMs === undefined ||
|
|
1171
|
+
nodeStateTracker.state === UnreferencedState.Active) {
|
|
1016
1172
|
return;
|
|
1017
1173
|
}
|
|
1018
1174
|
// We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
|
|
@@ -1044,9 +1200,17 @@ export class GarbageCollector {
|
|
|
1044
1200
|
// Events generated:
|
|
1045
1201
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1046
1202
|
if (usageType === "Loaded") {
|
|
1047
|
-
|
|
1203
|
+
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePathToTelemetryProperty(packagePath), stack: generateStack() });
|
|
1204
|
+
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
1205
|
+
// detecting something wrong with GC either from the partner or from the runtime itself.
|
|
1206
|
+
if (state === UnreferencedState.Inactive) {
|
|
1207
|
+
this.mc.logger.sendTelemetryEvent(event);
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
this.mc.logger.sendErrorEvent(event);
|
|
1211
|
+
}
|
|
1048
1212
|
}
|
|
1049
|
-
// If SweepReady Usage Detection is
|
|
1213
|
+
// If SweepReady Usage Detection is enabled, the handler may close the interactive container.
|
|
1050
1214
|
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
1051
1215
|
// and errors will arise elsewhere in the runtime
|
|
1052
1216
|
if (state === UnreferencedState.SweepReady) {
|
|
@@ -1069,43 +1233,29 @@ export class GarbageCollector {
|
|
|
1069
1233
|
* revived and a Revived event will be logged for it.
|
|
1070
1234
|
*/
|
|
1071
1235
|
const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
|
|
1072
|
-
const active = nodeStateTracker === undefined ||
|
|
1236
|
+
const active = nodeStateTracker === undefined ||
|
|
1237
|
+
nodeStateTracker.state === UnreferencedState.Active;
|
|
1073
1238
|
if ((usageType === "Revived") === active) {
|
|
1074
1239
|
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
1075
|
-
const fromPkg = eventProps.fromId
|
|
1076
|
-
|
|
1240
|
+
const fromPkg = eventProps.fromId
|
|
1241
|
+
? await this.getNodePackagePath(eventProps.fromId)
|
|
1242
|
+
: undefined;
|
|
1243
|
+
const event = Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: pkg
|
|
1244
|
+
? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
|
|
1245
|
+
: undefined, fromPkg: fromPkg
|
|
1246
|
+
? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
|
|
1247
|
+
: undefined });
|
|
1248
|
+
if (state === UnreferencedState.Inactive) {
|
|
1249
|
+
logger.sendTelemetryEvent(event);
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
logger.sendErrorEvent(event);
|
|
1253
|
+
}
|
|
1077
1254
|
}
|
|
1078
1255
|
}
|
|
1079
1256
|
this.pendingEventsQueue = [];
|
|
1080
1257
|
}
|
|
1081
1258
|
}
|
|
1082
|
-
/**
|
|
1083
|
-
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1084
|
-
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1085
|
-
*/
|
|
1086
|
-
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1087
|
-
let rootGCState = { gcNodes: {} };
|
|
1088
|
-
let tombstones;
|
|
1089
|
-
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1090
|
-
if (key === gcTombstoneBlobKey) {
|
|
1091
|
-
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1092
|
-
continue;
|
|
1093
|
-
}
|
|
1094
|
-
// Skip blobs that do not start with the GC prefix.
|
|
1095
|
-
if (!key.startsWith(gcBlobPrefix)) {
|
|
1096
|
-
continue;
|
|
1097
|
-
}
|
|
1098
|
-
const blobId = gcSnapshotTree.blobs[key];
|
|
1099
|
-
if (blobId === undefined) {
|
|
1100
|
-
continue;
|
|
1101
|
-
}
|
|
1102
|
-
const gcState = await readAndParseBlob(blobId);
|
|
1103
|
-
assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
|
|
1104
|
-
// Merge the GC state of this blob into the root GC state.
|
|
1105
|
-
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1106
|
-
}
|
|
1107
|
-
return { gcState: rootGCState, tombstones };
|
|
1108
|
-
}
|
|
1109
1259
|
function generateSortedGCState(gcState) {
|
|
1110
1260
|
const sortableArray = Object.entries(gcState.gcNodes);
|
|
1111
1261
|
sortableArray.sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -1120,7 +1270,9 @@ function generateSortedGCState(gcState) {
|
|
|
1120
1270
|
class TimerWithNoDefaultTimeout extends Timer {
|
|
1121
1271
|
constructor(callback) {
|
|
1122
1272
|
// The default timeout/handlers will never be used since start/restart pass overrides below
|
|
1123
|
-
super(0, () => {
|
|
1273
|
+
super(0, () => {
|
|
1274
|
+
throw new Error("DefaultHandler should not be used");
|
|
1275
|
+
});
|
|
1124
1276
|
this.callback = callback;
|
|
1125
1277
|
}
|
|
1126
1278
|
start(timeoutMs) {
|