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