@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 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 +62 -28
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +256 -102
- 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 +110 -78
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +336 -331
- 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 +40 -23
- 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 +69 -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 +57 -42
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +371 -239
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +23 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -0
- package/dist/garbageCollectionConstants.js +36 -0
- package/dist/garbageCollectionConstants.js.map +1 -0
- 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 +15 -11
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +42 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -0
- package/dist/opLifecycle/batchManager.js +124 -0
- package/dist/opLifecycle/batchManager.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +64 -0
- package/dist/opLifecycle/definitions.d.ts.map +1 -0
- package/dist/opLifecycle/definitions.js +7 -0
- package/dist/opLifecycle/definitions.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +12 -0
- package/dist/opLifecycle/index.d.ts.map +1 -0
- package/dist/opLifecycle/index.js +22 -0
- package/dist/opLifecycle/index.js.map +1 -0
- package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opCompressor.js +67 -0
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
- package/dist/opLifecycle/opDecompressor.js.map +1 -0
- package/dist/opLifecycle/opSplitter.d.ts +49 -0
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
- package/dist/opLifecycle/opSplitter.js +173 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -0
- package/dist/opLifecycle/outbox.d.ts +52 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -0
- package/dist/opLifecycle/outbox.js +164 -0
- package/dist/opLifecycle/outbox.js.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.js +96 -0
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
- 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 +62 -28
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +259 -105
- 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 +110 -78
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +340 -334
- 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 +41 -24
- 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 +75 -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 +57 -42
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +364 -232
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +23 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -0
- package/lib/garbageCollectionConstants.js +33 -0
- package/lib/garbageCollectionConstants.js.map +1 -0
- 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 +15 -11
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +42 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -0
- package/lib/opLifecycle/batchManager.js +120 -0
- package/lib/opLifecycle/batchManager.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +64 -0
- package/lib/opLifecycle/definitions.d.ts.map +1 -0
- package/lib/opLifecycle/definitions.js +6 -0
- package/lib/opLifecycle/definitions.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +12 -0
- package/lib/opLifecycle/index.d.ts.map +1 -0
- package/lib/opLifecycle/index.js +11 -0
- package/lib/opLifecycle/index.js.map +1 -0
- package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opCompressor.js +63 -0
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
- package/lib/opLifecycle/opDecompressor.js.map +1 -0
- package/lib/opLifecycle/opSplitter.d.ts +49 -0
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
- package/lib/opLifecycle/opSplitter.js +168 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -0
- package/lib/opLifecycle/outbox.d.ts +52 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -0
- package/lib/opLifecycle/outbox.js +160 -0
- package/lib/opLifecycle/outbox.js.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.js +91 -0
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
- 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 +28 -38
- package/prettier.config.cjs +1 -1
- package/src/batchTracker.ts +55 -50
- package/src/blobManager.ts +802 -541
- package/src/connectionTelemetry.ts +280 -249
- package/src/containerHandleContext.ts +27 -29
- package/src/containerRuntime.ts +3125 -2982
- package/src/dataStore.ts +172 -159
- package/src/dataStoreContext.ts +1049 -992
- package/src/dataStoreContexts.ts +178 -161
- package/src/dataStoreRegistry.ts +25 -20
- package/src/dataStores.ts +785 -711
- package/src/deltaScheduler.ts +158 -150
- package/src/garbageCollection.ts +1797 -1558
- package/src/garbageCollectionConstants.ts +38 -0
- package/src/garbageCollectionHelpers.ts +37 -0
- package/src/gcSweepReadyUsageDetection.ts +90 -84
- package/src/index.ts +68 -69
- package/src/opLifecycle/batchManager.ts +167 -0
- package/src/opLifecycle/definitions.ts +70 -0
- package/src/opLifecycle/index.ts +18 -0
- package/src/opLifecycle/opCompressor.ts +82 -0
- package/src/opLifecycle/opDecompressor.ts +124 -0
- package/src/opLifecycle/opSplitter.ts +238 -0
- package/src/opLifecycle/outbox.ts +228 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +106 -0
- 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/dist/batchManager.d.ts +0 -42
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js +0 -83
- package/dist/batchManager.js.map +0 -1
- package/dist/opCompressor.d.ts.map +0 -1
- package/dist/opCompressor.js +0 -50
- package/dist/opCompressor.js.map +0 -1
- package/dist/opDecompressor.d.ts.map +0 -1
- package/dist/opDecompressor.js.map +0 -1
- package/lib/batchManager.d.ts +0 -42
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js +0 -79
- package/lib/batchManager.js.map +0 -1
- package/lib/opCompressor.d.ts.map +0 -1
- package/lib/opCompressor.js +0 -46
- package/lib/opCompressor.js.map +0 -1
- package/lib/opDecompressor.d.ts.map +0 -1
- package/lib/opDecompressor.js.map +0 -1
- package/src/batchManager.ts +0 -108
- package/src/opCompressor.ts +0 -59
- package/src/opDecompressor.ts +0 -82
package/lib/garbageCollection.js
CHANGED
|
@@ -14,44 +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 { currentGCVersion, defaultInactiveTimeoutMs, defaultSessionExpiryDurationMs, disableSweepLogKey, disableTombstoneKey, gcVersionUpgradeToV2Key, gcTestModeKey, oneDayMs, runGCKey, runSessionExpiryKey, runSweepKey, stableGCVersion, trackGCStateKey, } from "./garbageCollectionConstants";
|
|
26
|
+
import { sendGCUnexpectedUsageEvent } from "./garbageCollectionHelpers";
|
|
25
27
|
import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
|
|
26
28
|
import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
|
|
27
|
-
/** This is the current version of garbage collection. */
|
|
28
|
-
const GCVersion = 1;
|
|
29
|
-
// The key for the GC tree in summary.
|
|
30
|
-
export const gcTreeKey = "gc";
|
|
31
|
-
// They prefix for GC blobs in the GC tree in summary.
|
|
32
|
-
export const gcBlobPrefix = "__gc";
|
|
33
|
-
// The key for tombstone blob in the GC tree in summary.
|
|
34
|
-
export const gcTombstoneBlobKey = "__tombstones";
|
|
35
|
-
// Feature gate key to turn GC on / off.
|
|
36
|
-
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
37
|
-
// Feature gate key to turn GC sweep on / off.
|
|
38
|
-
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
39
|
-
// Feature gate key to turn GC test mode on / off.
|
|
40
|
-
export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
41
|
-
// Feature gate key to expire a session after a set period of time.
|
|
42
|
-
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
43
|
-
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
44
|
-
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
45
|
-
// Feature gate key to turn GC sweep log off.
|
|
46
|
-
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
47
|
-
// Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
|
|
48
|
-
export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
|
|
49
|
-
// Feature gate to enable throwing an error when tombstone object is used.
|
|
50
|
-
export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
|
|
51
|
-
// One day in milliseconds.
|
|
52
|
-
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
53
|
-
export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
|
|
54
|
-
export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
|
|
55
29
|
/** The types of GC nodes in the GC reference graph. */
|
|
56
30
|
export const GCNodeType = {
|
|
57
31
|
// Nodes that are for data stores.
|
|
@@ -164,25 +138,13 @@ export class UnreferencedStateTracker {
|
|
|
164
138
|
export class GarbageCollector {
|
|
165
139
|
constructor(createParams) {
|
|
166
140
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
167
|
-
/**
|
|
168
|
-
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
169
|
-
*
|
|
170
|
-
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
171
|
-
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
172
|
-
*
|
|
173
|
-
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
174
|
-
* a document and the first time GC is enabled after is was disabled before.
|
|
175
|
-
*
|
|
176
|
-
* Note that the state needs reset only for the very first time summary is generated by this client. After that, the
|
|
177
|
-
* state will be up-to-date and this flag will be reset.
|
|
178
|
-
*/
|
|
179
|
-
this.initialStateNeedsReset = false;
|
|
180
|
-
// The current GC version that this container is running.
|
|
181
|
-
this.currentGCVersion = GCVersion;
|
|
182
141
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
183
142
|
// outbound routes from that node.
|
|
184
143
|
this.newReferencesSinceLastRun = new Map();
|
|
144
|
+
// A list of nodes that have been tombstoned.
|
|
185
145
|
this.tombstones = [];
|
|
146
|
+
// A list of nodes that have been deleted during sweep phase.
|
|
147
|
+
this.deletedNodes = new Set();
|
|
186
148
|
// Map of node ids to their unreferenced state tracker.
|
|
187
149
|
this.unreferencedNodesState = new Map();
|
|
188
150
|
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
@@ -202,7 +164,14 @@ export class GarbageCollector {
|
|
|
202
164
|
const baseSnapshot = createParams.baseSnapshot;
|
|
203
165
|
const metadata = createParams.metadata;
|
|
204
166
|
const readAndParseBlob = createParams.readAndParseBlob;
|
|
205
|
-
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;
|
|
206
175
|
this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
207
176
|
let prevSummaryGCVersion;
|
|
208
177
|
/**
|
|
@@ -216,8 +185,8 @@ export class GarbageCollector {
|
|
|
216
185
|
function computeSweepTimeout(sessionExpiryTimeoutMs) {
|
|
217
186
|
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
218
187
|
const bufferMs = oneDayMs;
|
|
219
|
-
return sessionExpiryTimeoutMs &&
|
|
220
|
-
|
|
188
|
+
return (sessionExpiryTimeoutMs &&
|
|
189
|
+
sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
221
190
|
}
|
|
222
191
|
/**
|
|
223
192
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
@@ -248,11 +217,11 @@ export class GarbageCollector {
|
|
|
248
217
|
// flag in GC options to false.
|
|
249
218
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
250
219
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
251
|
-
|
|
252
|
-
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
220
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true;
|
|
253
221
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
254
222
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
255
|
-
this.sessionExpiryTimeoutMs =
|
|
223
|
+
this.sessionExpiryTimeoutMs =
|
|
224
|
+
(_c = this.gcOptions.sessionExpiryTimeoutMs) !== null && _c !== void 0 ? _c : defaultSessionExpiryDurationMs;
|
|
256
225
|
}
|
|
257
226
|
this.sweepTimeoutMs =
|
|
258
227
|
testOverrideSweepTimeoutMs !== null && testOverrideSweepTimeoutMs !== void 0 ? testOverrideSweepTimeoutMs : computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
@@ -262,7 +231,9 @@ export class GarbageCollector {
|
|
|
262
231
|
// If Test Override config is set, override Session Expiry timeout.
|
|
263
232
|
const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
264
233
|
const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
|
|
265
|
-
this.sessionExpiryTimer = new Timer(timeoutMs, () => {
|
|
234
|
+
this.sessionExpiryTimer = new Timer(timeoutMs, () => {
|
|
235
|
+
this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
|
|
236
|
+
});
|
|
266
237
|
this.sessionExpiryTimer.start();
|
|
267
238
|
}
|
|
268
239
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -277,11 +248,12 @@ export class GarbageCollector {
|
|
|
277
248
|
*
|
|
278
249
|
* These conditions can be overridden via runGCKey feature flag.
|
|
279
250
|
*/
|
|
280
|
-
this.shouldRunGC =
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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);
|
|
285
257
|
/**
|
|
286
258
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
287
259
|
*
|
|
@@ -293,24 +265,26 @@ export class GarbageCollector {
|
|
|
293
265
|
* feature flag.
|
|
294
266
|
*/
|
|
295
267
|
this.shouldRunSweep =
|
|
296
|
-
this.shouldRunGC
|
|
297
|
-
|
|
298
|
-
|
|
268
|
+
this.shouldRunGC &&
|
|
269
|
+
this.sweepTimeoutMs !== undefined &&
|
|
270
|
+
((_e = this.mc.config.getBoolean(runSweepKey)) !== null && _e !== void 0 ? _e : this.sweepEnabled);
|
|
299
271
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
300
272
|
// Override inactive timeout if test config or gc options to override it is set.
|
|
301
|
-
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;
|
|
302
275
|
// Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
|
|
303
276
|
if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
|
|
304
277
|
throw new UsageError("inactive timeout should not be greater than the sweep timeout");
|
|
305
278
|
}
|
|
306
279
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
307
|
-
this.testMode =
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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;
|
|
314
288
|
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
315
289
|
// it involves fetching blobs from storage which is expensive.
|
|
316
290
|
this.baseSnapshotDataP = new LazyPromise(async () => {
|
|
@@ -327,11 +301,13 @@ export class GarbageCollector {
|
|
|
327
301
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
328
302
|
// consolidate into IGarbageCollectionState format.
|
|
329
303
|
// Add a node for the root node that is not present in older snapshot format.
|
|
330
|
-
const gcState = {
|
|
304
|
+
const gcState = {
|
|
305
|
+
gcNodes: { "/": { outboundRoutes: [] } },
|
|
306
|
+
};
|
|
331
307
|
const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
|
|
332
308
|
assert(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
333
309
|
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
334
|
-
const blobId = dsSnapshotTree.blobs[
|
|
310
|
+
const blobId = dsSnapshotTree.blobs[gcTreeKey];
|
|
335
311
|
if (blobId === undefined) {
|
|
336
312
|
continue;
|
|
337
313
|
}
|
|
@@ -351,14 +327,19 @@ export class GarbageCollector {
|
|
|
351
327
|
// Prefix the data store id to the GC node ids to make them relative to the root from being
|
|
352
328
|
// relative to the data store. Similar to how its done in DataStore::getGCData.
|
|
353
329
|
const rootId = id === "/" ? dsRootId : `${dsRootId}${id}`;
|
|
354
|
-
gcState.gcNodes[rootId] = {
|
|
330
|
+
gcState.gcNodes[rootId] = {
|
|
331
|
+
outboundRoutes: Array.from(outboundRoutes),
|
|
332
|
+
};
|
|
355
333
|
}
|
|
356
334
|
assert(gcState.gcNodes[dsRootId] !== undefined, 0x2a9 /* GC nodes for data store not in GC blob */);
|
|
357
|
-
gcState.gcNodes[dsRootId].unreferencedTimestampMs =
|
|
335
|
+
gcState.gcNodes[dsRootId].unreferencedTimestampMs =
|
|
336
|
+
gcSummaryDetails.unrefTimestamp;
|
|
358
337
|
}
|
|
359
338
|
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
360
339
|
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
361
|
-
return Object.keys(gcState.gcNodes).length === 1
|
|
340
|
+
return Object.keys(gcState.gcNodes).length === 1
|
|
341
|
+
? undefined
|
|
342
|
+
: { gcState, tombstones: undefined, deletedNodes: undefined };
|
|
362
343
|
}
|
|
363
344
|
catch (error) {
|
|
364
345
|
const dpe = DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
@@ -372,7 +353,6 @@ export class GarbageCollector {
|
|
|
372
353
|
* GC state and updates their inactive or sweep ready state.
|
|
373
354
|
*/
|
|
374
355
|
this.initializeGCStateFromBaseSnapshotP = new LazyPromise(async () => {
|
|
375
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
376
356
|
/**
|
|
377
357
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
378
358
|
* how long objects have been unreferenced and if they can be deleted.
|
|
@@ -381,6 +361,7 @@ export class GarbageCollector {
|
|
|
381
361
|
* for this container and it is in read mode. In this scenario, there is no point in running GC anyway
|
|
382
362
|
* because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
383
363
|
*/
|
|
364
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
384
365
|
if (currentReferenceTimestampMs === undefined) {
|
|
385
366
|
// Log an event so we can evaluate how often we run into this scenario.
|
|
386
367
|
this.mc.logger.sendErrorEvent({
|
|
@@ -389,38 +370,24 @@ export class GarbageCollector {
|
|
|
389
370
|
});
|
|
390
371
|
return;
|
|
391
372
|
}
|
|
392
|
-
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
393
373
|
/**
|
|
394
374
|
* The base snapshot data will not be present if the container is loaded from:
|
|
395
375
|
* 1. The first summary created by the detached container.
|
|
396
376
|
* 2. A summary that was generated with GC disabled.
|
|
397
377
|
* 3. A summary that was generated before GC even existed.
|
|
398
378
|
*/
|
|
379
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
399
380
|
if (baseSnapshotData === undefined) {
|
|
400
381
|
return;
|
|
401
382
|
}
|
|
402
|
-
|
|
403
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
404
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
405
|
-
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
406
|
-
}
|
|
407
|
-
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
408
|
-
}
|
|
409
|
-
this.previousGCDataFromLastRun = { gcNodes };
|
|
410
|
-
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
411
|
-
if (this.trackGCState) {
|
|
412
|
-
this.latestSummaryData = {
|
|
413
|
-
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
414
|
-
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
415
|
-
};
|
|
416
|
-
}
|
|
383
|
+
this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
|
|
417
384
|
});
|
|
418
|
-
// Get the GC details
|
|
419
|
-
//
|
|
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.
|
|
420
387
|
this.baseGCDetailsP = new LazyPromise(async () => {
|
|
421
388
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
422
389
|
if (baseSnapshotData === undefined) {
|
|
423
|
-
return
|
|
390
|
+
return {};
|
|
424
391
|
}
|
|
425
392
|
const gcNodes = {};
|
|
426
393
|
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
@@ -430,18 +397,7 @@ export class GarbageCollector {
|
|
|
430
397
|
// This is an optimization for space (vs performance) wherein we don't need to store the used routes of
|
|
431
398
|
// each node in the summary.
|
|
432
399
|
const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
|
|
433
|
-
|
|
434
|
-
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
435
|
-
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
436
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
437
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
438
|
-
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
439
|
-
if (dataStoreGCDetails !== undefined) {
|
|
440
|
-
dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return baseGCDetailsMap;
|
|
400
|
+
return { gcData: { gcNodes }, usedRoutes };
|
|
445
401
|
});
|
|
446
402
|
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
447
403
|
// summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
|
|
@@ -462,39 +418,143 @@ export class GarbageCollector {
|
|
|
462
418
|
*
|
|
463
419
|
* 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
|
|
464
420
|
*
|
|
465
|
-
* 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:
|
|
466
424
|
*
|
|
467
|
-
*
|
|
425
|
+
* 4.1. The summary this client loaded with has data from a different GC version.
|
|
468
426
|
*
|
|
469
|
-
*
|
|
427
|
+
* 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
470
428
|
*/
|
|
471
429
|
get summaryStateNeedsReset() {
|
|
472
|
-
return this.
|
|
473
|
-
(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;
|
|
474
449
|
}
|
|
475
450
|
/** Returns a list of all the configurations for garbage collection. */
|
|
476
451
|
get configs() {
|
|
477
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);
|
|
478
453
|
}
|
|
479
454
|
/**
|
|
480
|
-
* Called during container initialization. Initialize the tombstone state
|
|
481
|
-
*
|
|
482
|
-
* 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.
|
|
483
457
|
*/
|
|
484
458
|
async initializeBaseState() {
|
|
485
459
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
486
460
|
/**
|
|
487
|
-
* The base snapshot data
|
|
461
|
+
* The base snapshot data will not be present if the container is loaded from:
|
|
488
462
|
* 1. The first summary created by the detached container.
|
|
489
463
|
* 2. A summary that was generated with GC disabled.
|
|
490
464
|
* 3. A summary that was generated before GC even existed.
|
|
491
|
-
* 4. A summary that was generated with tombstone feature disabled.
|
|
492
465
|
*/
|
|
493
|
-
if (
|
|
466
|
+
if (baseSnapshotData === undefined) {
|
|
494
467
|
return;
|
|
495
468
|
}
|
|
496
|
-
|
|
497
|
-
|
|
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
|
+
}
|
|
498
558
|
}
|
|
499
559
|
/**
|
|
500
560
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -528,7 +588,9 @@ export class GarbageCollector {
|
|
|
528
588
|
var _a;
|
|
529
589
|
const fullGC = (_a = options.fullGC) !== null && _a !== void 0 ? _a : (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
|
|
530
590
|
const logger = options.logger
|
|
531
|
-
? ChildLogger.create(options.logger, undefined, {
|
|
591
|
+
? ChildLogger.create(options.logger, undefined, {
|
|
592
|
+
all: { completedGCRuns: () => this.completedRuns },
|
|
593
|
+
})
|
|
532
594
|
: this.mc.logger;
|
|
533
595
|
/**
|
|
534
596
|
* If there is no current reference timestamp, skip running GC. We need the current timestamp to track
|
|
@@ -580,13 +642,13 @@ export class GarbageCollector {
|
|
|
580
642
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
581
643
|
// involving access to deleted data.
|
|
582
644
|
if (this.testMode) {
|
|
583
|
-
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds
|
|
645
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
584
646
|
}
|
|
585
647
|
else if (this.tombstoneMode) {
|
|
586
|
-
// If we are running in GC tombstone mode,
|
|
587
|
-
//
|
|
588
|
-
// Note: we will not tombstone in test mode
|
|
589
|
-
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);
|
|
590
652
|
}
|
|
591
653
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
592
654
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
@@ -601,30 +663,44 @@ export class GarbageCollector {
|
|
|
601
663
|
*/
|
|
602
664
|
summarize(fullTree, trackState, telemetryContext) {
|
|
603
665
|
var _a;
|
|
604
|
-
if (!this.shouldRunGC || this.
|
|
666
|
+
if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
|
|
605
667
|
return;
|
|
606
668
|
}
|
|
607
669
|
const gcState = { gcNodes: {} };
|
|
608
|
-
for (const [nodeId, outboundRoutes] of Object.entries(this.
|
|
670
|
+
for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
|
|
609
671
|
gcState.gcNodes[nodeId] = {
|
|
610
672
|
outboundRoutes,
|
|
611
673
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
612
674
|
};
|
|
613
675
|
}
|
|
614
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.
|
|
615
683
|
const serializedTombstones = this.tombstoneMode
|
|
616
|
-
?
|
|
684
|
+
? this.tombstones.length > 0
|
|
685
|
+
? JSON.stringify(this.tombstones.sort())
|
|
686
|
+
: undefined
|
|
617
687
|
: undefined;
|
|
618
688
|
/**
|
|
619
|
-
* Incremental summary of GC data - If
|
|
620
|
-
*
|
|
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.
|
|
621
693
|
*/
|
|
622
694
|
if (this.trackGCState) {
|
|
623
|
-
this.pendingSummaryData = {
|
|
695
|
+
this.pendingSummaryData = {
|
|
696
|
+
serializedGCState,
|
|
697
|
+
serializedTombstones,
|
|
698
|
+
serializedDeletedNodes,
|
|
699
|
+
};
|
|
624
700
|
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
625
|
-
// If
|
|
626
|
-
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
627
|
-
|
|
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) {
|
|
628
704
|
const stats = mergeStats();
|
|
629
705
|
stats.handleNodeCount++;
|
|
630
706
|
return {
|
|
@@ -636,24 +712,25 @@ export class GarbageCollector {
|
|
|
636
712
|
stats,
|
|
637
713
|
};
|
|
638
714
|
}
|
|
639
|
-
// If
|
|
640
|
-
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 */);
|
|
641
717
|
}
|
|
642
718
|
}
|
|
643
719
|
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
644
|
-
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
720
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, false /* trackState */);
|
|
645
721
|
}
|
|
646
722
|
/**
|
|
647
|
-
* Builds the GC summary tree which contains GC state and
|
|
648
|
-
* If trackState is false,
|
|
649
|
-
* 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.
|
|
650
726
|
* @param serializedGCState - The GC state serialized as string.
|
|
651
|
-
* @param serializedTombstones -
|
|
727
|
+
* @param serializedTombstones - The tombstone state serialized as string.
|
|
728
|
+
* @param serializedDeletedNodes - Deleted nodes serialized as string.
|
|
652
729
|
* @param trackState - Whether we are tracking GC state across summaries.
|
|
653
730
|
* @returns the GC summary tree.
|
|
654
731
|
*/
|
|
655
|
-
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
656
|
-
var _a, _b;
|
|
732
|
+
buildGCSummaryTree(serializedGCState, serializedTombstones, serializedDeletedNodes, trackState) {
|
|
733
|
+
var _a, _b, _c;
|
|
657
734
|
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
658
735
|
const builder = new SummaryTreeBuilder();
|
|
659
736
|
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
@@ -663,16 +740,28 @@ export class GarbageCollector {
|
|
|
663
740
|
else {
|
|
664
741
|
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
665
742
|
}
|
|
666
|
-
// If
|
|
667
|
-
|
|
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) {
|
|
668
756
|
return builder.getSummaryTree();
|
|
669
757
|
}
|
|
670
|
-
// If the
|
|
671
|
-
if (((
|
|
672
|
-
|
|
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}`);
|
|
673
762
|
}
|
|
674
763
|
else {
|
|
675
|
-
builder.addBlob(
|
|
764
|
+
builder.addBlob(gcDeletedBlobKey, serializedDeletedNodes);
|
|
676
765
|
}
|
|
677
766
|
return builder.getSummaryTree();
|
|
678
767
|
}
|
|
@@ -689,50 +778,58 @@ export class GarbageCollector {
|
|
|
689
778
|
};
|
|
690
779
|
}
|
|
691
780
|
/**
|
|
692
|
-
* Returns a
|
|
693
|
-
*
|
|
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.
|
|
694
783
|
*/
|
|
695
784
|
async getBaseGCDetails() {
|
|
696
785
|
return this.baseGCDetailsP;
|
|
697
786
|
}
|
|
698
787
|
/**
|
|
699
|
-
* Called
|
|
700
|
-
*
|
|
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.
|
|
701
790
|
*/
|
|
702
|
-
async
|
|
703
|
-
|
|
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) {
|
|
704
800
|
return;
|
|
705
801
|
}
|
|
706
802
|
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
707
|
-
//
|
|
803
|
+
// Update latest state from pending.
|
|
708
804
|
if (result.wasSummaryTracked) {
|
|
709
805
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
710
|
-
this.initialStateNeedsReset = false;
|
|
711
806
|
if (this.trackGCState) {
|
|
712
807
|
this.latestSummaryData = this.pendingSummaryData;
|
|
713
808
|
this.pendingSummaryData = undefined;
|
|
714
809
|
}
|
|
715
810
|
return;
|
|
716
811
|
}
|
|
717
|
-
// If the summary was not tracked by this client,
|
|
718
|
-
// 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.
|
|
719
813
|
const snapshot = result.snapshot;
|
|
720
814
|
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
721
815
|
if (metadataBlobId) {
|
|
722
816
|
const metadata = await readAndParseBlob(metadataBlobId);
|
|
723
817
|
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
724
818
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
731
|
-
};
|
|
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) });
|
|
732
824
|
}
|
|
733
|
-
|
|
734
|
-
|
|
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);
|
|
735
831
|
}
|
|
832
|
+
this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
|
|
736
833
|
this.pendingSummaryData = undefined;
|
|
737
834
|
}
|
|
738
835
|
/**
|
|
@@ -771,6 +868,30 @@ export class GarbageCollector {
|
|
|
771
868
|
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
772
869
|
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
773
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);
|
|
774
895
|
}
|
|
775
896
|
dispose() {
|
|
776
897
|
var _a;
|
|
@@ -787,7 +908,7 @@ export class GarbageCollector {
|
|
|
787
908
|
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
788
909
|
*/
|
|
789
910
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
790
|
-
this.
|
|
911
|
+
this.gcDataFromLastRun = cloneGCData(gcData);
|
|
791
912
|
this.tombstones = [];
|
|
792
913
|
this.newReferencesSinceLastRun.clear();
|
|
793
914
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
@@ -823,27 +944,31 @@ export class GarbageCollector {
|
|
|
823
944
|
}
|
|
824
945
|
/**
|
|
825
946
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
826
|
-
* time.
|
|
827
|
-
*
|
|
947
|
+
* time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
|
|
948
|
+
* updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
|
|
949
|
+
* these objects while there can be in-memory referenced to it:
|
|
950
|
+
* 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
|
|
951
|
+
* added, the object may have been accessed and in-memory reference to it added.
|
|
952
|
+
* 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
|
|
953
|
+
* unreferenced, they could have been accessed and in-memory reference to them added.
|
|
828
954
|
*
|
|
829
955
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
830
956
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
831
957
|
*/
|
|
832
958
|
updateStateSinceLastRun(currentGCData, logger) {
|
|
833
959
|
// If we haven't run GC before there is nothing to do.
|
|
834
|
-
if (this.
|
|
960
|
+
if (this.gcDataFromLastRun === undefined) {
|
|
835
961
|
return;
|
|
836
962
|
}
|
|
837
963
|
// Find any references that haven't been identified correctly.
|
|
838
|
-
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.
|
|
964
|
+
const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.gcDataFromLastRun, this.newReferencesSinceLastRun);
|
|
839
965
|
if (missingExplicitReferences.length > 0) {
|
|
840
966
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
841
|
-
|
|
967
|
+
logger.sendErrorEvent({
|
|
842
968
|
eventName: "gcUnknownOutboundReferences",
|
|
843
969
|
gcNodeId: missingExplicitReference[0],
|
|
844
970
|
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
845
|
-
};
|
|
846
|
-
logger.sendPerformanceEvent(event);
|
|
971
|
+
});
|
|
847
972
|
});
|
|
848
973
|
}
|
|
849
974
|
// No references were added since the last run so we don't have to update reference states of any unreferenced
|
|
@@ -857,21 +982,18 @@ export class GarbageCollector {
|
|
|
857
982
|
* run, and then add the references since last run.
|
|
858
983
|
*
|
|
859
984
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
860
|
-
*
|
|
861
985
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
862
|
-
* references added new outbound references before
|
|
986
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
863
987
|
*
|
|
864
988
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
865
|
-
* references added new outbound references before
|
|
989
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
866
990
|
*
|
|
867
991
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
868
|
-
*
|
|
869
|
-
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
870
|
-
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
871
|
-
*
|
|
992
|
+
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
872
993
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
873
994
|
*/
|
|
874
|
-
const gcDataSuperSet = concatGarbageCollectionData(this.
|
|
995
|
+
const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
|
|
996
|
+
const newOutboundRoutesSinceLastRun = [];
|
|
875
997
|
this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
|
|
876
998
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
877
999
|
gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
|
|
@@ -879,19 +1001,25 @@ export class GarbageCollector {
|
|
|
879
1001
|
else {
|
|
880
1002
|
gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
|
|
881
1003
|
}
|
|
1004
|
+
newOutboundRoutesSinceLastRun.push(...outboundRoutes);
|
|
882
1005
|
});
|
|
883
1006
|
/**
|
|
884
|
-
* Run GC on the above reference graph
|
|
1007
|
+
* Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
|
|
1008
|
+
* list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
|
|
885
1009
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
886
|
-
*
|
|
1010
|
+
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
1011
|
+
* unreferenced and add unreferenced state.
|
|
887
1012
|
*/
|
|
888
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
|
|
1013
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, [
|
|
1014
|
+
"/",
|
|
1015
|
+
...newOutboundRoutesSinceLastRun,
|
|
1016
|
+
]);
|
|
889
1017
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
890
1018
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
891
1019
|
if (nodeStateTracker !== undefined) {
|
|
892
1020
|
// Stop tracking so as to clear out any running timers.
|
|
893
1021
|
nodeStateTracker.stopTracking();
|
|
894
|
-
// Delete the
|
|
1022
|
+
// Delete the unreferenced state as we don't need to track it any more.
|
|
895
1023
|
this.unreferencedNodesState.delete(nodeId);
|
|
896
1024
|
}
|
|
897
1025
|
}
|
|
@@ -910,7 +1038,7 @@ export class GarbageCollector {
|
|
|
910
1038
|
* @returns - a list of missing explicit references
|
|
911
1039
|
*/
|
|
912
1040
|
findMissingExplicitReferences(currentGCData, previousGCData, explicitReferences) {
|
|
913
|
-
assert(previousGCData !== undefined, 0x2b7);
|
|
1041
|
+
assert(previousGCData !== undefined, 0x2b7 /* "Can't validate correctness without GC data from last run" */);
|
|
914
1042
|
const currentGraph = Object.entries(currentGCData.gcNodes);
|
|
915
1043
|
const missingExplicitReferences = [];
|
|
916
1044
|
currentGraph.forEach(([nodeId, currentOutboundRoutes]) => {
|
|
@@ -918,14 +1046,20 @@ export class GarbageCollector {
|
|
|
918
1046
|
const previousRoutes = (_a = previousGCData.gcNodes[nodeId]) !== null && _a !== void 0 ? _a : [];
|
|
919
1047
|
const explicitRoutes = (_b = explicitReferences.get(nodeId)) !== null && _b !== void 0 ? _b : [];
|
|
920
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
|
+
*/
|
|
921
1057
|
currentOutboundRoutes.forEach((route) => {
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
notRouteFromDDSToParentDataStore &&
|
|
928
|
-
(!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)) {
|
|
929
1063
|
missingExplicitRoutes.push(route);
|
|
930
1064
|
}
|
|
931
1065
|
});
|
|
@@ -957,7 +1091,7 @@ export class GarbageCollector {
|
|
|
957
1091
|
gcStats.nodeCount++;
|
|
958
1092
|
// If there is no previous GC data, every node's state is generated and is considered as updated.
|
|
959
1093
|
// Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
|
|
960
|
-
const stateUpdated = this.
|
|
1094
|
+
const stateUpdated = this.gcDataFromLastRun === undefined ||
|
|
961
1095
|
this.unreferencedNodesState.has(nodeId) === referenced;
|
|
962
1096
|
if (stateUpdated) {
|
|
963
1097
|
gcStats.updatedNodeCount++;
|
|
@@ -997,7 +1131,8 @@ export class GarbageCollector {
|
|
|
997
1131
|
* this will give us a view into how much deleted content a container has.
|
|
998
1132
|
*/
|
|
999
1133
|
logSweepEvents(logger, currentReferenceTimestampMs) {
|
|
1000
|
-
if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
1134
|
+
if (this.mc.config.getBoolean(disableSweepLogKey) === true ||
|
|
1135
|
+
this.sweepTimeoutMs === undefined) {
|
|
1001
1136
|
return;
|
|
1002
1137
|
}
|
|
1003
1138
|
this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
|
|
@@ -1032,7 +1167,8 @@ export class GarbageCollector {
|
|
|
1032
1167
|
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
1033
1168
|
// logging as nothing interesting would have happened worth logging.
|
|
1034
1169
|
// If the node is active, skip logging.
|
|
1035
|
-
if (currentReferenceTimestampMs === undefined ||
|
|
1170
|
+
if (currentReferenceTimestampMs === undefined ||
|
|
1171
|
+
nodeStateTracker.state === UnreferencedState.Active) {
|
|
1036
1172
|
return;
|
|
1037
1173
|
}
|
|
1038
1174
|
// We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
|
|
@@ -1064,9 +1200,17 @@ export class GarbageCollector {
|
|
|
1064
1200
|
// Events generated:
|
|
1065
1201
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1066
1202
|
if (usageType === "Loaded") {
|
|
1067
|
-
|
|
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
|
+
}
|
|
1068
1212
|
}
|
|
1069
|
-
// If SweepReady Usage Detection is
|
|
1213
|
+
// If SweepReady Usage Detection is enabled, the handler may close the interactive container.
|
|
1070
1214
|
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
1071
1215
|
// and errors will arise elsewhere in the runtime
|
|
1072
1216
|
if (state === UnreferencedState.SweepReady) {
|
|
@@ -1089,43 +1233,29 @@ export class GarbageCollector {
|
|
|
1089
1233
|
* revived and a Revived event will be logged for it.
|
|
1090
1234
|
*/
|
|
1091
1235
|
const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
|
|
1092
|
-
const active = nodeStateTracker === undefined ||
|
|
1236
|
+
const active = nodeStateTracker === undefined ||
|
|
1237
|
+
nodeStateTracker.state === UnreferencedState.Active;
|
|
1093
1238
|
if ((usageType === "Revived") === active) {
|
|
1094
1239
|
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
1095
|
-
const fromPkg = eventProps.fromId
|
|
1096
|
-
|
|
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
|
+
}
|
|
1097
1254
|
}
|
|
1098
1255
|
}
|
|
1099
1256
|
this.pendingEventsQueue = [];
|
|
1100
1257
|
}
|
|
1101
1258
|
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1104
|
-
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1105
|
-
*/
|
|
1106
|
-
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1107
|
-
let rootGCState = { gcNodes: {} };
|
|
1108
|
-
let tombstones;
|
|
1109
|
-
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1110
|
-
if (key === gcTombstoneBlobKey) {
|
|
1111
|
-
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
// Skip blobs that do not start with the GC prefix.
|
|
1115
|
-
if (!key.startsWith(gcBlobPrefix)) {
|
|
1116
|
-
continue;
|
|
1117
|
-
}
|
|
1118
|
-
const blobId = gcSnapshotTree.blobs[key];
|
|
1119
|
-
if (blobId === undefined) {
|
|
1120
|
-
continue;
|
|
1121
|
-
}
|
|
1122
|
-
const gcState = await readAndParseBlob(blobId);
|
|
1123
|
-
assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
|
|
1124
|
-
// Merge the GC state of this blob into the root GC state.
|
|
1125
|
-
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1126
|
-
}
|
|
1127
|
-
return { gcState: rootGCState, tombstones };
|
|
1128
|
-
}
|
|
1129
1259
|
function generateSortedGCState(gcState) {
|
|
1130
1260
|
const sortableArray = Object.entries(gcState.gcNodes);
|
|
1131
1261
|
sortableArray.sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -1140,7 +1270,9 @@ function generateSortedGCState(gcState) {
|
|
|
1140
1270
|
class TimerWithNoDefaultTimeout extends Timer {
|
|
1141
1271
|
constructor(callback) {
|
|
1142
1272
|
// The default timeout/handlers will never be used since start/restart pass overrides below
|
|
1143
|
-
super(0, () => {
|
|
1273
|
+
super(0, () => {
|
|
1274
|
+
throw new Error("DefaultHandler should not be used");
|
|
1275
|
+
});
|
|
1144
1276
|
this.callback = callback;
|
|
1145
1277
|
}
|
|
1146
1278
|
start(timeoutMs) {
|