@fluidframework/container-runtime 2.0.0-internal.2.0.2 → 2.0.0-internal.2.1.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/dist/containerRuntime.d.ts +2 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +63 -23
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +6 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +7 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +34 -8
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.js +1 -1
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStores.d.ts +3 -2
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +29 -3
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +19 -5
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +120 -37
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.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/summarizerClientElection.js +1 -1
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +3 -2
- package/dist/summaryGenerator.js.map +1 -1
- package/garbageCollection.md +27 -22
- package/lib/containerRuntime.d.ts +2 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +64 -24
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +6 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +7 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +35 -9
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.js +1 -1
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStores.d.ts +3 -2
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +30 -4
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +19 -5
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +119 -36
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.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/summarizerClientElection.js +1 -1
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +3 -2
- package/lib/summaryGenerator.js.map +1 -1
- package/package.json +26 -23
- package/src/containerRuntime.ts +77 -26
- package/src/dataStore.ts +13 -1
- package/src/dataStoreContext.ts +48 -10
- package/src/dataStoreContexts.ts +1 -1
- package/src/dataStores.ts +34 -3
- package/src/garbageCollection.ts +144 -44
- package/src/index.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/summarizerClientElection.ts +1 -1
- package/src/summaryGenerator.ts +3 -2
package/lib/garbageCollection.js
CHANGED
|
@@ -30,6 +30,8 @@ const GCVersion = 1;
|
|
|
30
30
|
export const gcTreeKey = "gc";
|
|
31
31
|
// They prefix for GC blobs in the GC tree in summary.
|
|
32
32
|
export const gcBlobPrefix = "__gc";
|
|
33
|
+
// The key for tombstone blob in the GC tree in summary.
|
|
34
|
+
export const gcTombstoneBlobKey = "__tombstones";
|
|
33
35
|
// Feature gate key to turn GC on / off.
|
|
34
36
|
export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
35
37
|
// Feature gate key to turn GC sweep on / off.
|
|
@@ -42,6 +44,8 @@ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
|
42
44
|
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
43
45
|
// Feature gate key to turn GC sweep log off.
|
|
44
46
|
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
47
|
+
// Feature gate key to tombstone datastores.
|
|
48
|
+
export const testTombstoneKey = "Fluid.GarbageCollection.Test.Tombstone";
|
|
45
49
|
// One day in milliseconds.
|
|
46
50
|
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
47
51
|
export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
|
|
@@ -157,7 +161,7 @@ export class UnreferencedStateTracker {
|
|
|
157
161
|
*/
|
|
158
162
|
export class GarbageCollector {
|
|
159
163
|
constructor(createParams) {
|
|
160
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
164
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
161
165
|
/**
|
|
162
166
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
163
167
|
*
|
|
@@ -176,6 +180,7 @@ export class GarbageCollector {
|
|
|
176
180
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
177
181
|
// outbound routes from that node.
|
|
178
182
|
this.newReferencesSinceLastRun = new Map();
|
|
183
|
+
this.tombstones = [];
|
|
179
184
|
// Map of node ids to their unreferenced state tracker.
|
|
180
185
|
this.unreferencedNodesState = new Map();
|
|
181
186
|
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
@@ -297,6 +302,7 @@ export class GarbageCollector {
|
|
|
297
302
|
}
|
|
298
303
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
299
304
|
this.testMode = (_h = this.mc.config.getBoolean(gcTestModeKey)) !== null && _h !== void 0 ? _h : this.gcOptions.runGCInTestMode === true;
|
|
305
|
+
this.tombstoneMode = (_j = this.mc.config.getBoolean(testTombstoneKey)) !== null && _j !== void 0 ? _j : false;
|
|
300
306
|
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
301
307
|
// contain GC tree and GC is enabled.
|
|
302
308
|
const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
|
|
@@ -312,11 +318,17 @@ export class GarbageCollector {
|
|
|
312
318
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
313
319
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
314
320
|
if (gcSnapshotTree !== undefined) {
|
|
315
|
-
const
|
|
321
|
+
const baseGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
322
|
+
if (baseGCData.tombstones !== undefined && this.tombstoneMode) {
|
|
323
|
+
this.tombstones = baseGCData.tombstones;
|
|
324
|
+
}
|
|
316
325
|
if (this.trackGCState) {
|
|
317
|
-
this.
|
|
326
|
+
this.latestSummaryData = {
|
|
327
|
+
serializedGCState: JSON.stringify(generateSortedGCState(baseGCData.gcState)),
|
|
328
|
+
serializedTombstones: JSON.stringify(baseGCData.tombstones),
|
|
329
|
+
};
|
|
318
330
|
}
|
|
319
|
-
return
|
|
331
|
+
return baseGCData.gcState;
|
|
320
332
|
}
|
|
321
333
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
322
334
|
// consolidate into IGarbageCollectionState format.
|
|
@@ -461,7 +473,7 @@ export class GarbageCollector {
|
|
|
461
473
|
}
|
|
462
474
|
/** Returns a list of all the configurations for garbage collection. */
|
|
463
475
|
get configs() {
|
|
464
|
-
return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, sweepTimeout: this.sweepTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
|
|
476
|
+
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);
|
|
465
477
|
}
|
|
466
478
|
/**
|
|
467
479
|
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
@@ -471,15 +483,19 @@ export class GarbageCollector {
|
|
|
471
483
|
*/
|
|
472
484
|
setConnectionState(connected, clientId) {
|
|
473
485
|
/**
|
|
474
|
-
* For
|
|
486
|
+
* For all clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
475
487
|
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
476
488
|
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
477
489
|
* could affect the GC state will have been processed.
|
|
478
490
|
*
|
|
491
|
+
* If GC is up-to-date for the client and the summarizing client, there will be an doubling of both
|
|
492
|
+
* InactiveObject_Loaded and SweepReady_Loaded errors, as there will be one from the sending client and one from
|
|
493
|
+
* the receiving summarizer client.
|
|
494
|
+
*
|
|
479
495
|
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
480
496
|
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
481
497
|
*/
|
|
482
|
-
if (this.activeConnection() &&
|
|
498
|
+
if (this.activeConnection() && this.shouldRunGC) {
|
|
483
499
|
this.initializeBaseStateP.catch((error) => { });
|
|
484
500
|
}
|
|
485
501
|
}
|
|
@@ -545,6 +561,22 @@ export class GarbageCollector {
|
|
|
545
561
|
if (this.testMode) {
|
|
546
562
|
this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
|
|
547
563
|
}
|
|
564
|
+
else {
|
|
565
|
+
// If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
|
|
566
|
+
// scenarios involving access to "deleted" data without actually deleting the data from summaries.
|
|
567
|
+
// Note: we will not tombstone in test mode
|
|
568
|
+
if (this.tombstoneMode) {
|
|
569
|
+
const tombstoneRoutes = [];
|
|
570
|
+
// Currently only tombstone datastores
|
|
571
|
+
for (const [key, value] of this.unreferencedNodesState.entries()) {
|
|
572
|
+
if (value.state === UnreferencedState.SweepReady &&
|
|
573
|
+
this.runtime.getNodeType(key) === GCNodeType.DataStore) {
|
|
574
|
+
tombstoneRoutes.push(key);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
this.runtime.deleteUnusedRoutes(tombstoneRoutes);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
548
580
|
// Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
|
|
549
581
|
// updates its state so that we don't send false positives based on intermediate state. For example, we may get
|
|
550
582
|
// reference to an unreferenced node from another unreferenced node which means the node wasn't revived.
|
|
@@ -568,31 +600,67 @@ export class GarbageCollector {
|
|
|
568
600
|
unreferencedTimestampMs: (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.unreferencedTimestampMs,
|
|
569
601
|
};
|
|
570
602
|
}
|
|
571
|
-
const
|
|
603
|
+
const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
|
|
604
|
+
const serializedTombstones = this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined;
|
|
572
605
|
/**
|
|
573
|
-
*
|
|
574
|
-
*
|
|
606
|
+
* Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
|
|
607
|
+
* summary, send summary handles for them. Otherwise, send the data in summary blobs.
|
|
575
608
|
*/
|
|
576
609
|
if (this.trackGCState) {
|
|
577
|
-
this.
|
|
578
|
-
if (this.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
610
|
+
this.pendingSummaryData = { serializedGCState, serializedTombstones };
|
|
611
|
+
if (trackState && !fullTree && this.latestSummaryData !== undefined) {
|
|
612
|
+
// If neither GC state or tombstone state changed, send a summary handle for the entire GC data.
|
|
613
|
+
if (this.latestSummaryData.serializedGCState === serializedGCState
|
|
614
|
+
&& this.latestSummaryData.serializedTombstones === serializedTombstones) {
|
|
615
|
+
const stats = mergeStats();
|
|
616
|
+
stats.handleNodeCount++;
|
|
617
|
+
return {
|
|
618
|
+
summary: {
|
|
619
|
+
type: SummaryType.Handle,
|
|
620
|
+
handle: `/${gcTreeKey}`,
|
|
621
|
+
handleType: SummaryType.Tree,
|
|
622
|
+
},
|
|
623
|
+
stats,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
// If either or both of GC state or tombstone state changed, build a GC summary tree.
|
|
627
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, true /* trackState */);
|
|
592
628
|
}
|
|
593
629
|
}
|
|
630
|
+
// If not tracking GC state, build a GC summary tree without any summary handles.
|
|
631
|
+
return this.buildGCSummaryTree(serializedGCState, serializedTombstones, false /* trackState */);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Builds the GC summary tree which contains GC state and tombstone state.
|
|
635
|
+
* If trackState is false, both GC state and tombstone state are written as summary blobs.
|
|
636
|
+
* If trackState is true, summary blob is written for GC state or tombstone state if they changed.
|
|
637
|
+
* @param serializedGCState - The GC state serialized as string.
|
|
638
|
+
* @param serializedTombstones - THe tombstone state serialized as string.
|
|
639
|
+
* @param trackState - Whether we are tracking GC state across summaries.
|
|
640
|
+
* @returns the GC summary tree.
|
|
641
|
+
*/
|
|
642
|
+
buildGCSummaryTree(serializedGCState, serializedTombstones, trackState) {
|
|
643
|
+
var _a, _b;
|
|
644
|
+
const gcStateBlobKey = `${gcBlobPrefix}_root`;
|
|
594
645
|
const builder = new SummaryTreeBuilder();
|
|
595
|
-
|
|
646
|
+
// If the GC state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
647
|
+
if (((_a = this.latestSummaryData) === null || _a === void 0 ? void 0 : _a.serializedGCState) === serializedGCState && trackState) {
|
|
648
|
+
builder.addHandle(gcStateBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcStateBlobKey}`);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
builder.addBlob(gcStateBlobKey, serializedGCState);
|
|
652
|
+
}
|
|
653
|
+
// If there is no tombstone data, return only the GC state.
|
|
654
|
+
if (serializedTombstones === undefined) {
|
|
655
|
+
return builder.getSummaryTree();
|
|
656
|
+
}
|
|
657
|
+
// If the tombstone state hasn't changed, write a summary handle, else write a summary blob for it.
|
|
658
|
+
if (((_b = this.latestSummaryData) === null || _b === void 0 ? void 0 : _b.serializedTombstones) === serializedTombstones && trackState) {
|
|
659
|
+
builder.addHandle(gcTombstoneBlobKey, SummaryType.Blob, `/${gcTreeKey}/${gcTombstoneBlobKey}`);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
builder.addBlob(gcTombstoneBlobKey, serializedTombstones);
|
|
663
|
+
}
|
|
596
664
|
return builder.getSummaryTree();
|
|
597
665
|
}
|
|
598
666
|
getMetadata() {
|
|
@@ -628,8 +696,8 @@ export class GarbageCollector {
|
|
|
628
696
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
629
697
|
this.initialStateNeedsReset = false;
|
|
630
698
|
if (this.trackGCState) {
|
|
631
|
-
this.
|
|
632
|
-
this.
|
|
699
|
+
this.latestSummaryData = this.pendingSummaryData;
|
|
700
|
+
this.pendingSummaryData = undefined;
|
|
633
701
|
}
|
|
634
702
|
return;
|
|
635
703
|
}
|
|
@@ -643,13 +711,16 @@ export class GarbageCollector {
|
|
|
643
711
|
}
|
|
644
712
|
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
645
713
|
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
646
|
-
const
|
|
647
|
-
this.
|
|
714
|
+
const latestGCData = await getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
715
|
+
this.latestSummaryData = {
|
|
716
|
+
serializedGCState: JSON.stringify(generateSortedGCState(latestGCData.gcState)),
|
|
717
|
+
serializedTombstones: JSON.stringify(latestGCData.tombstones),
|
|
718
|
+
};
|
|
648
719
|
}
|
|
649
720
|
else {
|
|
650
|
-
this.
|
|
721
|
+
this.latestSummaryData = undefined;
|
|
651
722
|
}
|
|
652
|
-
this.
|
|
723
|
+
this.pendingSummaryData = undefined;
|
|
653
724
|
}
|
|
654
725
|
/**
|
|
655
726
|
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
@@ -704,6 +775,7 @@ export class GarbageCollector {
|
|
|
704
775
|
*/
|
|
705
776
|
updateCurrentState(gcData, gcResult, currentReferenceTimestampMs) {
|
|
706
777
|
this.previousGCDataFromLastRun = cloneGCData(gcData);
|
|
778
|
+
this.tombstones = [];
|
|
707
779
|
this.newReferencesSinceLastRun.clear();
|
|
708
780
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
709
781
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
@@ -727,6 +799,12 @@ export class GarbageCollector {
|
|
|
727
799
|
}
|
|
728
800
|
else {
|
|
729
801
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
802
|
+
if (this.tombstoneMode && nodeStateTracker.state === UnreferencedState.SweepReady) {
|
|
803
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
804
|
+
if (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) {
|
|
805
|
+
this.tombstones.push(nodeId);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
730
808
|
}
|
|
731
809
|
}
|
|
732
810
|
}
|
|
@@ -1011,12 +1089,17 @@ export class GarbageCollector {
|
|
|
1011
1089
|
}
|
|
1012
1090
|
}
|
|
1013
1091
|
/**
|
|
1014
|
-
* Gets the garbage collection
|
|
1015
|
-
* Merge the GC state from all such blobs
|
|
1092
|
+
* Gets the garbage collection data from the given snapshot tree. It contains GC state and tombstone state.
|
|
1093
|
+
* The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
|
|
1016
1094
|
*/
|
|
1017
|
-
async function
|
|
1095
|
+
async function getGCDataFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
1018
1096
|
let rootGCState = { gcNodes: {} };
|
|
1097
|
+
let tombstones;
|
|
1019
1098
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
1099
|
+
if (key === gcTombstoneBlobKey) {
|
|
1100
|
+
tombstones = await readAndParseBlob(gcSnapshotTree.blobs[key]);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1020
1103
|
// Skip blobs that do not start with the GC prefix.
|
|
1021
1104
|
if (!key.startsWith(gcBlobPrefix)) {
|
|
1022
1105
|
continue;
|
|
@@ -1030,7 +1113,7 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
|
1030
1113
|
// Merge the GC state of this blob into the root GC state.
|
|
1031
1114
|
rootGCState = concatGarbageCollectionStates(rootGCState, gcState);
|
|
1032
1115
|
}
|
|
1033
|
-
return rootGCState;
|
|
1116
|
+
return { gcState: rootGCState, tombstones };
|
|
1034
1117
|
}
|
|
1035
1118
|
function generateSortedGCState(gcState) {
|
|
1036
1119
|
const sortableArray = Object.entries(gcState.gcNodes);
|