@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +19 -8
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +44 -33
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +130 -97
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +39 -8
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +117 -61
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +4 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +9 -6
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +30 -24
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -20
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +205 -151
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +6 -3
- package/dist/garbageCollectionConstants.d.ts.map +1 -1
- package/dist/garbageCollectionConstants.js +7 -7
- package/dist/garbageCollectionConstants.js.map +1 -1
- package/dist/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/dist/garbageCollectionTombstoneUtils.js +28 -0
- package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +13 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +35 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +25 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +24 -10
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +30 -17
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts +34 -2
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +114 -5
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +5 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +24 -14
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +0 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +0 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +9 -20
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.d.ts +0 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +2 -1
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +1 -0
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +1 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +44 -33
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +131 -98
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +39 -8
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +115 -59
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +5 -4
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +9 -6
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +32 -26
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -20
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +201 -147
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +6 -3
- package/lib/garbageCollectionConstants.d.ts.map +1 -1
- package/lib/garbageCollectionConstants.js +6 -6
- package/lib/garbageCollectionConstants.js.map +1 -1
- package/lib/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/lib/garbageCollectionTombstoneUtils.js +24 -0
- package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +13 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +25 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +24 -10
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +30 -17
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts +34 -2
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +112 -4
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +5 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +24 -14
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +0 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +0 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +9 -20
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.d.ts +0 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +2 -1
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +1 -0
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +1 -2
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +37 -19
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +146 -103
- package/src/containerRuntime.ts +166 -65
- package/src/dataStoreContext.ts +5 -5
- package/src/dataStores.ts +40 -30
- package/src/garbageCollection.ts +254 -183
- package/src/garbageCollectionConstants.ts +7 -6
- package/src/garbageCollectionTombstoneUtils.ts +31 -0
- package/src/index.ts +0 -5
- package/src/opLifecycle/batchManager.ts +59 -1
- package/src/opLifecycle/definitions.ts +27 -1
- package/src/opLifecycle/index.ts +2 -1
- package/src/opLifecycle/opCompressor.ts +29 -12
- package/src/opLifecycle/opDecompressor.ts +39 -18
- package/src/opLifecycle/opSplitter.ts +141 -7
- package/src/opLifecycle/outbox.ts +32 -16
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +0 -1
- package/src/scheduleManager.ts +19 -30
- package/src/summarizer.ts +1 -1
- package/src/summarizerTypes.ts +1 -0
- package/src/summaryFormat.ts +1 -2
package/src/garbageCollection.ts
CHANGED
|
@@ -3,28 +3,32 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryLogger
|
|
6
|
+
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
8
8
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
9
9
|
import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
|
|
10
10
|
import { IRequestHeader } from "@fluidframework/core-interfaces";
|
|
11
11
|
import {
|
|
12
12
|
cloneGCData,
|
|
13
|
-
concatGarbageCollectionStates,
|
|
14
13
|
concatGarbageCollectionData,
|
|
14
|
+
getGCDataFromSnapshot,
|
|
15
15
|
IGCResult,
|
|
16
16
|
runGarbageCollection,
|
|
17
|
-
|
|
17
|
+
trimLeadingSlashes,
|
|
18
18
|
} from "@fluidframework/garbage-collector";
|
|
19
19
|
import { ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
gcTreeKey,
|
|
22
|
+
gcBlobPrefix,
|
|
23
|
+
gcTombstoneBlobKey,
|
|
22
24
|
IGarbageCollectionData,
|
|
23
|
-
IGarbageCollectionState,
|
|
24
25
|
IGarbageCollectionDetailsBase,
|
|
26
|
+
IGarbageCollectionSnapshotData,
|
|
27
|
+
IGarbageCollectionState,
|
|
25
28
|
ISummarizeResult,
|
|
26
29
|
ITelemetryContext,
|
|
27
30
|
IGarbageCollectionNodeData,
|
|
31
|
+
IGarbageCollectionSummaryDetailsLegacy,
|
|
28
32
|
ISummaryTreeWithStats,
|
|
29
33
|
} from "@fluidframework/runtime-definitions";
|
|
30
34
|
import {
|
|
@@ -46,18 +50,19 @@ import {
|
|
|
46
50
|
import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
|
|
47
51
|
import { getSummaryForDatastores } from "./dataStores";
|
|
48
52
|
import {
|
|
53
|
+
currentGCVersion,
|
|
49
54
|
defaultInactiveTimeoutMs,
|
|
50
55
|
defaultSessionExpiryDurationMs,
|
|
51
56
|
disableSweepLogKey,
|
|
52
57
|
disableTombstoneKey,
|
|
53
|
-
|
|
58
|
+
gcVersionUpgradeToV2Key,
|
|
54
59
|
gcTestModeKey,
|
|
55
|
-
gcTombstoneBlobKey,
|
|
56
|
-
gcTreeKey,
|
|
57
60
|
oneDayMs,
|
|
58
61
|
runGCKey,
|
|
59
62
|
runSessionExpiryKey,
|
|
60
63
|
runSweepKey,
|
|
64
|
+
stableGCVersion,
|
|
65
|
+
throwOnTombstoneUsageKey,
|
|
61
66
|
trackGCStateKey
|
|
62
67
|
} from "./garbageCollectionConstants";
|
|
63
68
|
import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
|
|
@@ -72,9 +77,6 @@ import {
|
|
|
72
77
|
ICreateContainerMetadata,
|
|
73
78
|
} from "./summaryFormat";
|
|
74
79
|
|
|
75
|
-
/** This is the current version of garbage collection. */
|
|
76
|
-
const GCVersion = 1;
|
|
77
|
-
|
|
78
80
|
/** The statistics of the system state after a garbage collection run. */
|
|
79
81
|
export interface IGCStats {
|
|
80
82
|
/** The number of nodes in the container. */
|
|
@@ -119,7 +121,9 @@ export interface IGarbageCollectionRuntime {
|
|
|
119
121
|
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
120
122
|
updateUsedRoutes(usedRoutes: string[]): void;
|
|
121
123
|
/** After GC has run, called to notify the runtime of routes that are unused in it. */
|
|
122
|
-
updateUnusedRoutes(unusedRoutes: string[]
|
|
124
|
+
updateUnusedRoutes(unusedRoutes: string[]): void;
|
|
125
|
+
/** Called to notify the runtime of routes that are tombstones. */
|
|
126
|
+
updateTombstonedRoutes(tombstoneRoutes: string[]): void;
|
|
123
127
|
/** Returns a referenced timestamp to be used to track unreferenced nodes. */
|
|
124
128
|
getCurrentReferenceTimestampMs(): number | undefined;
|
|
125
129
|
/** Returns the type of the GC node. */
|
|
@@ -149,10 +153,15 @@ export interface IGarbageCollector {
|
|
|
149
153
|
): ISummarizeResult | undefined;
|
|
150
154
|
/** Returns the garbage collector specific metadata to be written into the summary. */
|
|
151
155
|
getMetadata(): IGCMetadata;
|
|
152
|
-
/** Returns
|
|
153
|
-
getBaseGCDetails(): Promise<
|
|
156
|
+
/** Returns the GC details generated from the base snapshot. */
|
|
157
|
+
getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
|
|
154
158
|
/** Called when the latest summary of the system has been refreshed. */
|
|
155
|
-
|
|
159
|
+
refreshLatestSummary(
|
|
160
|
+
result: RefreshSummaryResult,
|
|
161
|
+
proposalHandle: string | undefined,
|
|
162
|
+
summaryRefSeq: number,
|
|
163
|
+
readAndParseBlob: ReadAndParseBlob,
|
|
164
|
+
): Promise<void>;
|
|
156
165
|
/** Called when a node is updated. Used to detect and log when an inactive node is changed or loaded. */
|
|
157
166
|
nodeUpdated(
|
|
158
167
|
nodePath: string,
|
|
@@ -219,14 +228,6 @@ interface IGCSummaryTrackingData {
|
|
|
219
228
|
serializedTombstones: string | undefined;
|
|
220
229
|
}
|
|
221
230
|
|
|
222
|
-
/**
|
|
223
|
-
* The GC data that is read from a snapshot. It contains the GC state and tombstone state.
|
|
224
|
-
*/
|
|
225
|
-
interface IGCSnapshotData {
|
|
226
|
-
gcState: IGarbageCollectionState;
|
|
227
|
-
tombstones: string[] | undefined;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
231
|
/**
|
|
231
232
|
* Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
|
|
232
233
|
* be deleted by the sweep phase.
|
|
@@ -347,14 +348,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
347
348
|
*
|
|
348
349
|
* 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
|
|
349
350
|
*
|
|
350
|
-
* 3.
|
|
351
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
351
352
|
*
|
|
352
|
-
*
|
|
353
|
+
* 4. The GC version in the latest summary is different from the current GC version. This can happen if:
|
|
353
354
|
*
|
|
354
|
-
*
|
|
355
|
+
* 4.1. The summary this client loaded with has data from a different GC version.
|
|
356
|
+
*
|
|
357
|
+
* 4.2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
355
358
|
*/
|
|
356
359
|
public get summaryStateNeedsReset(): boolean {
|
|
357
|
-
return this.
|
|
360
|
+
return this.gcStateNeedsReset ||
|
|
358
361
|
(this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion);
|
|
359
362
|
}
|
|
360
363
|
|
|
@@ -387,7 +390,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
387
390
|
private readonly mc: MonitoringContext;
|
|
388
391
|
|
|
389
392
|
/**
|
|
390
|
-
* Tells whether the
|
|
393
|
+
* Tells whether the GC state needs to be reset. This can happen under 3 conditions:
|
|
391
394
|
*
|
|
392
395
|
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
393
396
|
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
@@ -395,18 +398,24 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
395
398
|
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
396
399
|
* a document and the first time GC is enabled after is was disabled before.
|
|
397
400
|
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
401
|
+
* 3. GC is enabled and the latest summary state is refreshed from a snapshot that had GC disabled and vice-versa.
|
|
402
|
+
*
|
|
403
|
+
* Note that the state will be reset only once for the first summary generated after this returns true. After that,
|
|
404
|
+
* this will return false.
|
|
400
405
|
*/
|
|
401
|
-
private
|
|
406
|
+
private get gcStateNeedsReset(): boolean {
|
|
407
|
+
return this.wasGCRunInLatestSummary !== this.shouldRunGC;
|
|
408
|
+
}
|
|
409
|
+
// Tracks whether there was GC was run in latest summary being tracked.
|
|
410
|
+
private wasGCRunInLatestSummary: boolean;
|
|
402
411
|
|
|
403
412
|
// The current GC version that this container is running.
|
|
404
|
-
private readonly currentGCVersion
|
|
413
|
+
private readonly currentGCVersion: GCVersion;
|
|
405
414
|
// This is the version of GC data in the latest summary being tracked.
|
|
406
415
|
private latestSummaryGCVersion: GCVersion;
|
|
407
416
|
|
|
408
417
|
// Keeps track of the GC state from the last run.
|
|
409
|
-
private
|
|
418
|
+
private gcDataFromLastRun: IGarbageCollectionData | undefined;
|
|
410
419
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
411
420
|
// outbound routes from that node.
|
|
412
421
|
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
@@ -422,11 +431,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
422
431
|
private pendingSummaryData: IGCSummaryTrackingData | undefined;
|
|
423
432
|
|
|
424
433
|
// Promise when resolved returns the GC data data in the base snapshot.
|
|
425
|
-
private readonly baseSnapshotDataP: Promise<
|
|
434
|
+
private readonly baseSnapshotDataP: Promise<IGarbageCollectionSnapshotData | undefined>;
|
|
426
435
|
// Promise when resolved initializes the GC state from the data in the base snapshot.
|
|
427
436
|
private readonly initializeGCStateFromBaseSnapshotP: Promise<void>;
|
|
428
|
-
// The
|
|
429
|
-
private readonly baseGCDetailsP: Promise<
|
|
437
|
+
// The GC details generated from the base snapshot.
|
|
438
|
+
private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
|
|
430
439
|
// Map of node ids to their unreferenced state tracker.
|
|
431
440
|
private readonly unreferencedNodesState: Map<string, UnreferencedStateTracker> = new Map();
|
|
432
441
|
// The Timer responsible for closing the container when the session has expired
|
|
@@ -497,6 +506,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
497
506
|
createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } },
|
|
498
507
|
));
|
|
499
508
|
|
|
509
|
+
// If version upgrade is not enabled, fall back to the stable GC version.
|
|
510
|
+
this.currentGCVersion =
|
|
511
|
+
this.mc.config.getBoolean(gcVersionUpgradeToV2Key) === true ? currentGCVersion : stableGCVersion;
|
|
512
|
+
|
|
500
513
|
this.sweepReadyUsageHandler = new SweepReadyUsageDetectionHandler(
|
|
501
514
|
createParams.getContainerDiagnosticId(),
|
|
502
515
|
this.mc,
|
|
@@ -630,14 +643,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
630
643
|
// Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
|
|
631
644
|
this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
|
|
632
645
|
|
|
633
|
-
//
|
|
634
|
-
|
|
635
|
-
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
636
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
646
|
+
// If GC ran in the container that generated the base snapshot, it will have a GC tree.
|
|
647
|
+
this.wasGCRunInLatestSummary = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
637
648
|
|
|
638
649
|
// Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
|
|
639
650
|
// it involves fetching blobs from storage which is expensive.
|
|
640
|
-
this.baseSnapshotDataP = new LazyPromise<
|
|
651
|
+
this.baseSnapshotDataP = new LazyPromise<IGarbageCollectionSnapshotData | undefined>(async () => {
|
|
641
652
|
if (baseSnapshot === undefined) {
|
|
642
653
|
return undefined;
|
|
643
654
|
}
|
|
@@ -660,12 +671,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
660
671
|
assert(dataStoreSnapshotTree !== undefined,
|
|
661
672
|
0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
662
673
|
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
663
|
-
const blobId = dsSnapshotTree.blobs[
|
|
674
|
+
const blobId = dsSnapshotTree.blobs[gcTreeKey];
|
|
664
675
|
if (blobId === undefined) {
|
|
665
676
|
continue;
|
|
666
677
|
}
|
|
667
678
|
|
|
668
|
-
const gcSummaryDetails = await readAndParseBlob<
|
|
679
|
+
const gcSummaryDetails = await readAndParseBlob<IGarbageCollectionSummaryDetailsLegacy>(blobId);
|
|
669
680
|
// If there are no nodes for this data store, skip it.
|
|
670
681
|
if (gcSummaryDetails.gcData?.gcNodes === undefined) {
|
|
671
682
|
continue;
|
|
@@ -710,7 +721,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
710
721
|
* GC state and updates their inactive or sweep ready state.
|
|
711
722
|
*/
|
|
712
723
|
this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
|
|
713
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
714
724
|
/**
|
|
715
725
|
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
716
726
|
* how long objects have been unreferenced and if they can be deleted.
|
|
@@ -719,6 +729,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
719
729
|
* for this container and it is in read mode. In this scenario, there is no point in running GC anyway
|
|
720
730
|
* because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
721
731
|
*/
|
|
732
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
722
733
|
if (currentReferenceTimestampMs === undefined) {
|
|
723
734
|
// Log an event so we can evaluate how often we run into this scenario.
|
|
724
735
|
this.mc.logger.sendErrorEvent({
|
|
@@ -727,50 +738,25 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
727
738
|
});
|
|
728
739
|
return;
|
|
729
740
|
}
|
|
730
|
-
|
|
731
|
-
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
732
741
|
/**
|
|
733
742
|
* The base snapshot data will not be present if the container is loaded from:
|
|
734
743
|
* 1. The first summary created by the detached container.
|
|
735
744
|
* 2. A summary that was generated with GC disabled.
|
|
736
745
|
* 3. A summary that was generated before GC even existed.
|
|
737
746
|
*/
|
|
747
|
+
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
738
748
|
if (baseSnapshotData === undefined) {
|
|
739
749
|
return;
|
|
740
750
|
}
|
|
741
|
-
|
|
742
|
-
const gcNodes: { [id: string]: string[]; } = {};
|
|
743
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
744
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
745
|
-
this.unreferencedNodesState.set(
|
|
746
|
-
nodeId,
|
|
747
|
-
new UnreferencedStateTracker(
|
|
748
|
-
nodeData.unreferencedTimestampMs,
|
|
749
|
-
this.inactiveTimeoutMs,
|
|
750
|
-
currentReferenceTimestampMs,
|
|
751
|
-
this.sweepTimeoutMs,
|
|
752
|
-
),
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
756
|
-
}
|
|
757
|
-
this.previousGCDataFromLastRun = { gcNodes };
|
|
758
|
-
|
|
759
|
-
// If tracking state across summaries, update latest summary data from the base snapshot's GC data.
|
|
760
|
-
if (this.trackGCState) {
|
|
761
|
-
this.latestSummaryData = {
|
|
762
|
-
serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
|
|
763
|
-
serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
|
|
764
|
-
};
|
|
765
|
-
}
|
|
751
|
+
this.updateStateFromSnapshotData(baseSnapshotData, currentReferenceTimestampMs);
|
|
766
752
|
});
|
|
767
753
|
|
|
768
|
-
// Get the GC details
|
|
769
|
-
//
|
|
770
|
-
this.baseGCDetailsP = new LazyPromise<
|
|
754
|
+
// Get the GC details from the GC state in the base summary. This is returned in getBaseGCDetails which is
|
|
755
|
+
// used to initialize the GC state of all the nodes in the container.
|
|
756
|
+
this.baseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
|
|
771
757
|
const baseSnapshotData = await this.baseSnapshotDataP;
|
|
772
758
|
if (baseSnapshotData === undefined) {
|
|
773
|
-
return
|
|
759
|
+
return {};
|
|
774
760
|
}
|
|
775
761
|
|
|
776
762
|
const gcNodes: { [id: string]: string[]; } = {};
|
|
@@ -782,18 +768,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
782
768
|
// each node in the summary.
|
|
783
769
|
const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
|
|
784
770
|
|
|
785
|
-
|
|
786
|
-
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
787
|
-
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
788
|
-
for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
|
|
789
|
-
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
790
|
-
const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
|
|
791
|
-
if (dataStoreGCDetails !== undefined) {
|
|
792
|
-
dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return baseGCDetailsMap;
|
|
771
|
+
return { gcData: { gcNodes }, usedRoutes };
|
|
797
772
|
});
|
|
798
773
|
|
|
799
774
|
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
@@ -823,8 +798,79 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
823
798
|
if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
|
|
824
799
|
return;
|
|
825
800
|
}
|
|
826
|
-
this.tombstones = baseSnapshotData.tombstones;
|
|
827
|
-
this.runtime.
|
|
801
|
+
this.tombstones = Array.from(baseSnapshotData.tombstones);
|
|
802
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Update state from the given snapshot data. This is done during load and during refreshing state from a snapshot.
|
|
807
|
+
* All current tracking is reset and updated from the data in the snapshot.
|
|
808
|
+
* @param snapshotData - The snapshot data to update state from. If this is undefined, all GC state and tracking
|
|
809
|
+
* is reset.
|
|
810
|
+
* @param currentReferenceTimestampMs - The current reference timestamp for marking unreferenced nodes' unreferenced
|
|
811
|
+
* timestamp.
|
|
812
|
+
*/
|
|
813
|
+
private updateStateFromSnapshotData(
|
|
814
|
+
snapshotData: IGarbageCollectionSnapshotData | undefined,
|
|
815
|
+
currentReferenceTimestampMs: number,
|
|
816
|
+
) {
|
|
817
|
+
/**
|
|
818
|
+
* Note: "newReferencesSinceLastRun" is not reset here. This is done because there may be references since the
|
|
819
|
+
* snapshot that we are updating state from. For example, this client may have processed ops till seq#1000 and
|
|
820
|
+
* its refreshing state from a summary that happened at seq#900. In this case, there may be references between
|
|
821
|
+
* seq#901 and seq#1000 that we don't want to reset.
|
|
822
|
+
* Unfortunately, there is no way to track the seq# of ops that add references, so we choose to not reset any
|
|
823
|
+
* references here. This should be fine because, in the worst case, we may end up updating the unreferenced
|
|
824
|
+
* timestamp of a node which will delay its deletion. Although not ideal, this will only happen in rare
|
|
825
|
+
* scenarios, so it should be okay.
|
|
826
|
+
*/
|
|
827
|
+
|
|
828
|
+
// Clear all existing unreferenced state tracking.
|
|
829
|
+
for (const [, nodeStateTracker] of this.unreferencedNodesState) {
|
|
830
|
+
nodeStateTracker.stopTracking();
|
|
831
|
+
};
|
|
832
|
+
this.unreferencedNodesState.clear();
|
|
833
|
+
|
|
834
|
+
// If tombstone mode is enabled, update tombstone information and also update all tombstoned nodes in the
|
|
835
|
+
// container as per the state in the snapshot data.
|
|
836
|
+
if (this.tombstoneMode) {
|
|
837
|
+
this.tombstones = snapshotData?.tombstones ? Array.from(snapshotData.tombstones) : [];
|
|
838
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// If there is no snapshot data, it means this snapshot was generated with GC disabled. Unset all GC state.
|
|
842
|
+
if (snapshotData === undefined) {
|
|
843
|
+
this.gcDataFromLastRun = undefined;
|
|
844
|
+
this.latestSummaryData = undefined;
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Update unreferenced state tracking as per the GC state in the snapshot data and update gcDataFromLastRun
|
|
849
|
+
// to the GC data from the snapshot data.
|
|
850
|
+
const gcNodes: { [id: string]: string[]; } = {};
|
|
851
|
+
for (const [nodeId, nodeData] of Object.entries(snapshotData.gcState.gcNodes)) {
|
|
852
|
+
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
853
|
+
this.unreferencedNodesState.set(
|
|
854
|
+
nodeId,
|
|
855
|
+
new UnreferencedStateTracker(
|
|
856
|
+
nodeData.unreferencedTimestampMs,
|
|
857
|
+
this.inactiveTimeoutMs,
|
|
858
|
+
currentReferenceTimestampMs,
|
|
859
|
+
this.sweepTimeoutMs,
|
|
860
|
+
),
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
864
|
+
}
|
|
865
|
+
this.gcDataFromLastRun = { gcNodes };
|
|
866
|
+
|
|
867
|
+
// If tracking state across summaries, update latest summary data from the snapshot's GC data.
|
|
868
|
+
if (this.trackGCState) {
|
|
869
|
+
this.latestSummaryData = {
|
|
870
|
+
serializedGCState: JSON.stringify(generateSortedGCState(snapshotData.gcState)),
|
|
871
|
+
serializedTombstones: JSON.stringify(snapshotData.tombstones),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
828
874
|
}
|
|
829
875
|
|
|
830
876
|
/**
|
|
@@ -935,12 +981,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
935
981
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
936
982
|
// involving access to deleted data.
|
|
937
983
|
if (this.testMode) {
|
|
938
|
-
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds
|
|
984
|
+
this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds);
|
|
939
985
|
} else if (this.tombstoneMode) {
|
|
940
|
-
// If we are running in GC tombstone mode,
|
|
941
|
-
//
|
|
942
|
-
// Note: we will not tombstone in test mode
|
|
943
|
-
this.runtime.
|
|
986
|
+
// If we are running in GC tombstone mode, update tombstoned routes. This enables testing scenarios
|
|
987
|
+
// involving access to "deleted" data without actually deleting the data from summaries.
|
|
988
|
+
// Note: we will not tombstone in test mode.
|
|
989
|
+
this.runtime.updateTombstonedRoutes(this.tombstones);
|
|
944
990
|
}
|
|
945
991
|
|
|
946
992
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
@@ -961,12 +1007,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
961
1007
|
trackState: boolean,
|
|
962
1008
|
telemetryContext?: ITelemetryContext,
|
|
963
1009
|
): ISummarizeResult | undefined {
|
|
964
|
-
if (!this.shouldRunGC || this.
|
|
1010
|
+
if (!this.shouldRunGC || this.gcDataFromLastRun === undefined) {
|
|
965
1011
|
return;
|
|
966
1012
|
}
|
|
967
1013
|
|
|
968
1014
|
const gcState: IGarbageCollectionState = { gcNodes: {} };
|
|
969
|
-
for (const [nodeId, outboundRoutes] of Object.entries(this.
|
|
1015
|
+
for (const [nodeId, outboundRoutes] of Object.entries(this.gcDataFromLastRun.gcNodes)) {
|
|
970
1016
|
gcState.gcNodes[nodeId] = {
|
|
971
1017
|
outboundRoutes,
|
|
972
1018
|
unreferencedTimestampMs: this.unreferencedNodesState.get(nodeId)?.unreferencedTimestampMs,
|
|
@@ -1060,38 +1106,47 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1060
1106
|
}
|
|
1061
1107
|
|
|
1062
1108
|
/**
|
|
1063
|
-
* Returns a
|
|
1064
|
-
*
|
|
1109
|
+
* Returns a the GC details generated from the base summary. This is used to initialize the GC state of the nodes
|
|
1110
|
+
* in the container.
|
|
1065
1111
|
*/
|
|
1066
|
-
public async getBaseGCDetails(): Promise<
|
|
1112
|
+
public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
|
|
1067
1113
|
return this.baseGCDetailsP;
|
|
1068
1114
|
}
|
|
1069
1115
|
|
|
1070
1116
|
/**
|
|
1071
|
-
* Called
|
|
1072
|
-
*
|
|
1117
|
+
* Called to refresh the latest summary state. This happens when either a pending summary is acked or a snapshot
|
|
1118
|
+
* is downloaded and should be used to update the state.
|
|
1073
1119
|
*/
|
|
1074
|
-
public async
|
|
1120
|
+
public async refreshLatestSummary(
|
|
1075
1121
|
result: RefreshSummaryResult,
|
|
1122
|
+
proposalHandle: string | undefined,
|
|
1123
|
+
summaryRefSeq: number,
|
|
1076
1124
|
readAndParseBlob: ReadAndParseBlob,
|
|
1077
1125
|
): Promise<void> {
|
|
1078
|
-
|
|
1126
|
+
// If the latest summary was updated and the summary was tracked, this client is the one that generated this
|
|
1127
|
+
// summary. So, update wasGCRunInLatestSummary.
|
|
1128
|
+
// Note that this has to be updated if GC did not run too. Otherwise, `gcStateNeedsReset` will always return
|
|
1129
|
+
// true in scenarios where GC is disabled but enabled in the snapshot we loaded from.
|
|
1130
|
+
if (result.latestSummaryUpdated && result.wasSummaryTracked) {
|
|
1131
|
+
this.wasGCRunInLatestSummary = this.shouldRunGC;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (!result.latestSummaryUpdated || !this.shouldRunGC) {
|
|
1079
1135
|
return;
|
|
1080
1136
|
}
|
|
1081
1137
|
|
|
1082
1138
|
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
1083
|
-
//
|
|
1139
|
+
// Update latest state from pending.
|
|
1084
1140
|
if (result.wasSummaryTracked) {
|
|
1085
1141
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
1086
|
-
this.initialStateNeedsReset = false;
|
|
1087
1142
|
if (this.trackGCState) {
|
|
1088
1143
|
this.latestSummaryData = this.pendingSummaryData;
|
|
1089
1144
|
this.pendingSummaryData = undefined;
|
|
1090
1145
|
}
|
|
1091
1146
|
return;
|
|
1092
1147
|
}
|
|
1093
|
-
|
|
1094
|
-
//
|
|
1148
|
+
|
|
1149
|
+
// If the summary was not tracked by this client, the state should be updated from the downloaded snapshot.
|
|
1095
1150
|
const snapshot = result.snapshot;
|
|
1096
1151
|
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
1097
1152
|
if (metadataBlobId) {
|
|
@@ -1099,19 +1154,28 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1099
1154
|
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
1100
1155
|
}
|
|
1101
1156
|
|
|
1157
|
+
// The current reference timestamp should be available if we are refreshing state from a snapshot. There has
|
|
1158
|
+
// to be at least one op (summary op / ack, if nothing else) if a snapshot was taken.
|
|
1159
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
1160
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
1161
|
+
throw DataProcessingError.create(
|
|
1162
|
+
"No reference timestamp when updating GC state from snapshot",
|
|
1163
|
+
"refreshLatestSummary",
|
|
1164
|
+
undefined,
|
|
1165
|
+
{ proposalHandle, summaryRefSeq, details: JSON.stringify(this.configs) },
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1102
1168
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
1103
|
-
|
|
1104
|
-
|
|
1169
|
+
// If GC ran in the container that generated this snapshot, it will have a GC tree.
|
|
1170
|
+
this.wasGCRunInLatestSummary = gcSnapshotTree !== undefined;
|
|
1171
|
+
let latestGCData: IGarbageCollectionSnapshotData | undefined;
|
|
1172
|
+
if (gcSnapshotTree !== undefined) {
|
|
1173
|
+
latestGCData = await getGCDataFromSnapshot(
|
|
1105
1174
|
gcSnapshotTree,
|
|
1106
1175
|
readAndParseBlob,
|
|
1107
1176
|
);
|
|
1108
|
-
this.latestSummaryData = {
|
|
1109
|
-
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
1110
|
-
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
1111
|
-
};
|
|
1112
|
-
} else {
|
|
1113
|
-
this.latestSummaryData = undefined;
|
|
1114
1177
|
}
|
|
1178
|
+
this.updateStateFromSnapshotData(latestGCData, currentReferenceTimestampMs);
|
|
1115
1179
|
this.pendingSummaryData = undefined;
|
|
1116
1180
|
}
|
|
1117
1181
|
|
|
@@ -1168,6 +1232,25 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1168
1232
|
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
1169
1233
|
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
1170
1234
|
}
|
|
1235
|
+
|
|
1236
|
+
if (this.tombstones.includes(toNodePath)) {
|
|
1237
|
+
const nodeType = this.runtime.getNodeType(toNodePath)
|
|
1238
|
+
|
|
1239
|
+
let eventName = "GC_Tombstone_SubDatastore_Revived";
|
|
1240
|
+
if (nodeType === GCNodeType.DataStore) {
|
|
1241
|
+
eventName = "GC_Tombstone_Datastore_Revived";
|
|
1242
|
+
} else if (nodeType === GCNodeType.Blob) {
|
|
1243
|
+
eventName = "GC_Tombstone_Blob_Revived";
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1247
|
+
eventName,
|
|
1248
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
1249
|
+
url: trimLeadingSlashes(toNodePath),
|
|
1250
|
+
nodeType,
|
|
1251
|
+
throwOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey) ?? false,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1171
1254
|
}
|
|
1172
1255
|
|
|
1173
1256
|
public dispose(): void {
|
|
@@ -1189,7 +1272,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1189
1272
|
gcResult: IGCResult,
|
|
1190
1273
|
currentReferenceTimestampMs: number,
|
|
1191
1274
|
) {
|
|
1192
|
-
this.
|
|
1275
|
+
this.gcDataFromLastRun = cloneGCData(gcData);
|
|
1193
1276
|
this.tombstones = [];
|
|
1194
1277
|
this.newReferencesSinceLastRun.clear();
|
|
1195
1278
|
|
|
@@ -1235,33 +1318,37 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1235
1318
|
|
|
1236
1319
|
/**
|
|
1237
1320
|
* Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
|
|
1238
|
-
* time.
|
|
1239
|
-
*
|
|
1321
|
+
* time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
|
|
1322
|
+
* updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
|
|
1323
|
+
* these objects while there can be in-memory referenced to it:
|
|
1324
|
+
* 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
|
|
1325
|
+
* added, the object may have been accessed and in-memory reference to it added.
|
|
1326
|
+
* 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
|
|
1327
|
+
* unreferenced, they could have been accessed and in-memory reference to them added.
|
|
1240
1328
|
*
|
|
1241
1329
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
1242
1330
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
1243
1331
|
*/
|
|
1244
1332
|
private updateStateSinceLastRun(currentGCData: IGarbageCollectionData, logger: ITelemetryLogger) {
|
|
1245
1333
|
// If we haven't run GC before there is nothing to do.
|
|
1246
|
-
if (this.
|
|
1334
|
+
if (this.gcDataFromLastRun === undefined) {
|
|
1247
1335
|
return;
|
|
1248
1336
|
}
|
|
1249
1337
|
|
|
1250
1338
|
// Find any references that haven't been identified correctly.
|
|
1251
1339
|
const missingExplicitReferences = this.findMissingExplicitReferences(
|
|
1252
1340
|
currentGCData,
|
|
1253
|
-
this.
|
|
1341
|
+
this.gcDataFromLastRun,
|
|
1254
1342
|
this.newReferencesSinceLastRun,
|
|
1255
1343
|
);
|
|
1256
1344
|
|
|
1257
1345
|
if (missingExplicitReferences.length > 0) {
|
|
1258
1346
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
1259
|
-
|
|
1347
|
+
logger.sendErrorEvent({
|
|
1260
1348
|
eventName: "gcUnknownOutboundReferences",
|
|
1261
1349
|
gcNodeId: missingExplicitReference[0],
|
|
1262
1350
|
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
1263
|
-
};
|
|
1264
|
-
logger.sendPerformanceEvent(event);
|
|
1351
|
+
});
|
|
1265
1352
|
});
|
|
1266
1353
|
}
|
|
1267
1354
|
|
|
@@ -1277,41 +1364,41 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1277
1364
|
* run, and then add the references since last run.
|
|
1278
1365
|
*
|
|
1279
1366
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
1280
|
-
*
|
|
1281
1367
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
1282
|
-
* references added new outbound references before
|
|
1368
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
1283
1369
|
*
|
|
1284
1370
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
1285
|
-
* references added new outbound references before
|
|
1371
|
+
* references added new outbound references before they were deleted, we need to detect them.
|
|
1286
1372
|
*
|
|
1287
1373
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
1288
|
-
*
|
|
1289
|
-
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
1290
|
-
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
1291
|
-
*
|
|
1374
|
+
* - We don't require DDSes handles to be stored in a referenced DDS.
|
|
1292
1375
|
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
1293
1376
|
*/
|
|
1294
|
-
const gcDataSuperSet = concatGarbageCollectionData(this.
|
|
1377
|
+
const gcDataSuperSet = concatGarbageCollectionData(this.gcDataFromLastRun, currentGCData);
|
|
1378
|
+
const newOutboundRoutesSinceLastRun: string[] = [];
|
|
1295
1379
|
this.newReferencesSinceLastRun.forEach((outboundRoutes: string[], sourceNodeId: string) => {
|
|
1296
1380
|
if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
|
|
1297
1381
|
gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
|
|
1298
1382
|
} else {
|
|
1299
1383
|
gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
|
|
1300
1384
|
}
|
|
1385
|
+
newOutboundRoutesSinceLastRun.push(...outboundRoutes);
|
|
1301
1386
|
});
|
|
1302
1387
|
|
|
1303
1388
|
/**
|
|
1304
|
-
* Run GC on the above reference graph
|
|
1389
|
+
* Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
|
|
1390
|
+
* list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
|
|
1305
1391
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
1306
|
-
*
|
|
1392
|
+
* Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
|
|
1393
|
+
* unreferenced and add unreferenced state.
|
|
1307
1394
|
*/
|
|
1308
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
|
|
1395
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
|
|
1309
1396
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
1310
1397
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
1311
1398
|
if (nodeStateTracker !== undefined) {
|
|
1312
1399
|
// Stop tracking so as to clear out any running timers.
|
|
1313
1400
|
nodeStateTracker.stopTracking();
|
|
1314
|
-
// Delete the
|
|
1401
|
+
// Delete the unreferenced state as we don't need to track it any more.
|
|
1315
1402
|
this.unreferencedNodesState.delete(nodeId);
|
|
1316
1403
|
}
|
|
1317
1404
|
}
|
|
@@ -1346,17 +1433,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1346
1433
|
const previousRoutes = previousGCData.gcNodes[nodeId] ?? [];
|
|
1347
1434
|
const explicitRoutes = explicitReferences.get(nodeId) ?? [];
|
|
1348
1435
|
const missingExplicitRoutes: string[] = [];
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
|
|
1439
|
+
* explicit references should be added to missing explicit routes list.
|
|
1440
|
+
* 2. Only include data store and blob routes since GC only works for these two.
|
|
1441
|
+
* Note: Due to a bug with de-duped blobs, only adding data store routes for now.
|
|
1442
|
+
* 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
|
|
1443
|
+
* explicit routes to them.
|
|
1444
|
+
*/
|
|
1349
1445
|
currentOutboundRoutes.forEach((route) => {
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const notRouteFromDDSToParentDataStore = !nodeId.startsWith(route);
|
|
1355
|
-
if (
|
|
1356
|
-
isBlobOrDataStoreRoute &&
|
|
1357
|
-
notRouteFromDDSToParentDataStore &&
|
|
1358
|
-
(!previousRoutes.includes(route) && !explicitRoutes.includes(route))
|
|
1359
|
-
) {
|
|
1446
|
+
const nodeType = this.runtime.getNodeType(route);
|
|
1447
|
+
if ((nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob)
|
|
1448
|
+
&& !nodeId.startsWith(route)
|
|
1449
|
+
&& (!previousRoutes.includes(route) && !explicitRoutes.includes(route))) {
|
|
1360
1450
|
missingExplicitRoutes.push(route);
|
|
1361
1451
|
}
|
|
1362
1452
|
});
|
|
@@ -1391,7 +1481,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1391
1481
|
gcStats.nodeCount++;
|
|
1392
1482
|
// If there is no previous GC data, every node's state is generated and is considered as updated.
|
|
1393
1483
|
// Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
|
|
1394
|
-
const stateUpdated = this.
|
|
1484
|
+
const stateUpdated = this.gcDataFromLastRun === undefined ||
|
|
1395
1485
|
this.unreferencedNodesState.has(nodeId) === referenced;
|
|
1396
1486
|
if (stateUpdated) {
|
|
1397
1487
|
gcStats.updatedNodeCount++;
|
|
@@ -1530,12 +1620,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1530
1620
|
// Events generated:
|
|
1531
1621
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
1532
1622
|
if (usageType === "Loaded") {
|
|
1533
|
-
|
|
1623
|
+
const event = {
|
|
1534
1624
|
...propsToLog,
|
|
1535
1625
|
eventName: `${state}Object_${usageType}`,
|
|
1536
1626
|
pkg: packagePathToTelemetryProperty(packagePath),
|
|
1537
1627
|
stack: generateStack(),
|
|
1538
|
-
}
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
1631
|
+
// detecting something wrong with GC either from the partner or from the runtime itself.
|
|
1632
|
+
if (state === UnreferencedState.Inactive) {
|
|
1633
|
+
this.mc.logger.sendTelemetryEvent(event);
|
|
1634
|
+
} else {
|
|
1635
|
+
this.mc.logger.sendErrorEvent(event);
|
|
1636
|
+
}
|
|
1539
1637
|
}
|
|
1540
1638
|
|
|
1541
1639
|
// If SweepReady Usage Detection is enabed, the handler may close the interactive container.
|
|
@@ -1566,51 +1664,24 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1566
1664
|
if ((usageType === "Revived") === active) {
|
|
1567
1665
|
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
1568
1666
|
const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
|
|
1569
|
-
|
|
1667
|
+
const event = {
|
|
1570
1668
|
...propsToLog,
|
|
1571
1669
|
eventName: `${state}Object_${usageType}`,
|
|
1572
1670
|
pkg: pkg ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined,
|
|
1573
1671
|
fromPkg: fromPkg ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined,
|
|
1574
|
-
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
if (state === UnreferencedState.Inactive) {
|
|
1675
|
+
logger.sendTelemetryEvent(event);
|
|
1676
|
+
} else {
|
|
1677
|
+
logger.sendErrorEvent(event);
|
|
1678
|
+
}
|
|
1575
1679
|
}
|
|
1576
1680
|
}
|
|
1577
1681
|
this.pendingEventsQueue = [];
|
|
1578
1682
|
}
|
|
1579
1683
|
}
|
|
1580
1684
|
|
|
1581
|
-
/**
|
|
1582
|
-
* Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
|
|
1583
|
-
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1584
|
-
*/
|
|
1585
|
-
async function getGCDataFromSnapshot(
|
|
1586
|
-
gcSnapshotTree: ISnapshotTree,
|
|
1587
|
-
readAndParseBlob: ReadAndParseBlob,
|
|
1588
|
-
): Promise<IGCSnapshotData> {
|
|
1589
|
-
let rootGCState: IGarbageCollectionState = { gcNodes: {} };
|
|
1590
|
-
let tombstones: string[] | undefined;
|
|
1591
|
-
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1592
|
-
if (key === gcTombstoneBlobKey) {
|
|
1593
|
-
tombstones = await readAndParseBlob<string[]>(gcSnapshotTree.blobs[key]);
|
|
1594
|
-
continue;
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
// Skip blobs that do not start with the GC prefix.
|
|
1598
|
-
if (!key.startsWith(gcBlobPrefix)) {
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
const blobId = gcSnapshotTree.blobs[key];
|
|
1603
|
-
if (blobId === undefined) {
|
|
1604
|
-
continue;
|
|
1605
|
-
}
|
|
1606
|
-
const gcState = await readAndParseBlob<IGarbageCollectionState>(blobId);
|
|
1607
|
-
assert(gcState !== undefined, 0x2ad /* "GC blob missing from snapshot" */);
|
|
1608
|
-
// Merge the GC state of this blob into the root GC state.
|
|
1609
|
-
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1610
|
-
}
|
|
1611
|
-
return { gcState: rootGCState, tombstones };
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
1685
|
function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
|
|
1615
1686
|
const sortableArray: [string, IGarbageCollectionNodeData][] = Object.entries(gcState.gcNodes);
|
|
1616
1687
|
sortableArray.sort(([a], [b]) => a.localeCompare(b));
|