@fluidframework/container-runtime 1.2.7 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/dist/batchManager.d.ts +37 -0
- package/dist/batchManager.d.ts.map +1 -0
- package/dist/batchManager.js +73 -0
- package/dist/batchManager.js.map +1 -0
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +2 -3
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +87 -25
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +317 -99
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +109 -124
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +349 -542
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +29 -24
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +20 -14
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +49 -58
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +12 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +21 -20
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +6 -4
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +6 -4
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +74 -14
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +249 -170
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts +53 -0
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +126 -0
- package/dist/gcSweepReadyUsageDetection.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/opProperties.d.ts +7 -0
- package/dist/opProperties.d.ts.map +1 -0
- package/dist/opProperties.js +20 -0
- package/dist/opProperties.js.map +1 -0
- package/dist/orderedClientElection.d.ts +28 -10
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +14 -4
- package/dist/orderedClientElection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +0 -11
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +24 -46
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +14 -4
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +68 -26
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +31 -0
- package/dist/scheduleManager.d.ts.map +1 -0
- package/dist/scheduleManager.js +243 -0
- package/dist/scheduleManager.js.map +1 -0
- package/dist/summarizer.d.ts +0 -2
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +1 -12
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts +26 -4
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +95 -18
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +45 -18
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts +1 -0
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +31 -15
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryFormat.d.ts +0 -5
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts +1 -0
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +11 -9
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -2
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +22 -7
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchManager.d.ts +37 -0
- package/lib/batchManager.d.ts.map +1 -0
- package/lib/batchManager.js +69 -0
- package/lib/batchManager.js.map +1 -0
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +2 -3
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +87 -25
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +319 -101
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +109 -124
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +355 -547
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js +29 -24
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +20 -14
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +46 -55
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +12 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +21 -20
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +6 -4
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +6 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +74 -14
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +238 -160
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts +53 -0
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +121 -0
- package/lib/gcSweepReadyUsageDetection.js.map +1 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/opProperties.d.ts +7 -0
- package/lib/opProperties.d.ts.map +1 -0
- package/lib/opProperties.js +16 -0
- package/lib/opProperties.js.map +1 -0
- package/lib/orderedClientElection.d.ts +28 -10
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +14 -4
- package/lib/orderedClientElection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +0 -11
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +24 -46
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +14 -4
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +68 -26
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +31 -0
- package/lib/scheduleManager.d.ts.map +1 -0
- package/lib/scheduleManager.js +239 -0
- package/lib/scheduleManager.js.map +1 -0
- package/lib/summarizer.d.ts +0 -2
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +1 -12
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts +26 -4
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +95 -18
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +45 -18
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts +1 -0
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +31 -15
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryFormat.d.ts +0 -5
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts +1 -0
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +11 -9
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -2
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +22 -7
- package/lib/summaryManager.js.map +1 -1
- package/package.json +65 -24
- package/src/batchManager.ts +91 -0
- package/src/batchTracker.ts +2 -3
- package/src/blobManager.ts +385 -118
- package/src/containerRuntime.ts +529 -740
- package/src/dataStore.ts +49 -37
- package/src/dataStoreContext.ts +44 -56
- package/src/dataStores.ts +34 -30
- package/src/deltaScheduler.ts +6 -4
- package/src/garbageCollection.ts +297 -206
- package/src/gcSweepReadyUsageDetection.ts +139 -0
- package/src/index.ts +1 -2
- package/src/opProperties.ts +19 -0
- package/src/orderedClientElection.ts +31 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +27 -59
- package/src/runningSummarizer.ts +75 -22
- package/src/scheduleManager.ts +314 -0
- package/src/summarizer.ts +1 -18
- package/src/summarizerHeuristics.ts +133 -19
- package/src/summarizerTypes.ts +53 -18
- package/src/summaryCollection.ts +33 -18
- package/src/summaryFormat.ts +0 -6
- package/src/summaryGenerator.ts +40 -22
- package/src/summaryManager.ts +22 -7
- package/dist/opTelemetry.d.ts +0 -22
- package/dist/opTelemetry.d.ts.map +0 -1
- package/dist/opTelemetry.js +0 -59
- package/dist/opTelemetry.js.map +0 -1
- package/lib/opTelemetry.d.ts +0 -22
- package/lib/opTelemetry.d.ts.map +0 -1
- package/lib/opTelemetry.js +0 -55
- package/lib/opTelemetry.js.map +0 -1
- package/src/opTelemetry.ts +0 -71
|
@@ -15,7 +15,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
15
15
|
return t;
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.GarbageCollector = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.oneDayMs = exports.trackGCStateKey = exports.disableSessionExpiryKey = exports.runSessionExpiryKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
|
|
18
|
+
exports.GarbageCollector = exports.UnreferencedStateTracker = exports.UnreferencedState = exports.GCNodeType = exports.defaultSessionExpiryDurationMs = exports.defaultInactiveTimeoutMs = exports.oneDayMs = exports.disableSweepLogKey = exports.trackGCStateKey = exports.disableSessionExpiryKey = exports.runSessionExpiryKey = exports.gcTestModeKey = exports.runSweepKey = exports.runGCKey = exports.gcBlobPrefix = exports.gcTreeKey = void 0;
|
|
19
19
|
const common_utils_1 = require("@fluidframework/common-utils");
|
|
20
20
|
const container_utils_1 = require("@fluidframework/container-utils");
|
|
21
21
|
const garbage_collector_1 = require("@fluidframework/garbage-collector");
|
|
@@ -25,6 +25,7 @@ const runtime_utils_1 = require("@fluidframework/runtime-utils");
|
|
|
25
25
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
26
26
|
const containerRuntime_1 = require("./containerRuntime");
|
|
27
27
|
const dataStores_1 = require("./dataStores");
|
|
28
|
+
const gcSweepReadyUsageDetection_1 = require("./gcSweepReadyUsageDetection");
|
|
28
29
|
const summaryFormat_1 = require("./summaryFormat");
|
|
29
30
|
/** This is the current version of garbage collection. */
|
|
30
31
|
const GCVersion = 1;
|
|
@@ -33,24 +34,24 @@ exports.gcTreeKey = "gc";
|
|
|
33
34
|
// They prefix for GC blobs in the GC tree in summary.
|
|
34
35
|
exports.gcBlobPrefix = "__gc";
|
|
35
36
|
// Feature gate key to turn GC on / off.
|
|
36
|
-
|
|
37
|
+
exports.runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
37
38
|
// Feature gate key to turn GC sweep on / off.
|
|
38
|
-
|
|
39
|
+
exports.runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
39
40
|
// Feature gate key to turn GC test mode on / off.
|
|
40
|
-
|
|
41
|
+
exports.gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
41
42
|
// Feature gate key to write GC data at the root of the summary tree.
|
|
42
43
|
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
43
44
|
// Feature gate key to expire a session after a set period of time.
|
|
44
45
|
exports.runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
45
|
-
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present
|
|
46
|
+
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
|
|
46
47
|
exports.disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
47
48
|
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
48
49
|
exports.trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
49
50
|
// Feature gate key to turn GC sweep log off.
|
|
50
|
-
|
|
51
|
+
exports.disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
51
52
|
// One day in milliseconds.
|
|
52
53
|
exports.oneDayMs = 1 * 24 * 60 * 60 * 1000;
|
|
53
|
-
|
|
54
|
+
exports.defaultInactiveTimeoutMs = 7 * exports.oneDayMs; // 7 days
|
|
54
55
|
exports.defaultSessionExpiryDurationMs = 30 * exports.oneDayMs; // 30 days
|
|
55
56
|
/** The types of GC nodes in the GC reference graph. */
|
|
56
57
|
exports.GCNodeType = {
|
|
@@ -64,7 +65,7 @@ exports.GCNodeType = {
|
|
|
64
65
|
Other: "Other",
|
|
65
66
|
};
|
|
66
67
|
/** The state of node that is unreferenced. */
|
|
67
|
-
|
|
68
|
+
exports.UnreferencedState = {
|
|
68
69
|
/** The node is active, i.e., it can become referenced again. */
|
|
69
70
|
Active: "Active",
|
|
70
71
|
/** The node is inactive, i.e., it should not become referenced. */
|
|
@@ -80,19 +81,29 @@ class UnreferencedStateTracker {
|
|
|
80
81
|
constructor(unreferencedTimestampMs,
|
|
81
82
|
/** The time after which node transitions to Inactive state. */
|
|
82
83
|
inactiveTimeoutMs,
|
|
84
|
+
/** The current reference timestamp used to track how long this node has been unreferenced for. */
|
|
85
|
+
currentReferenceTimestampMs,
|
|
83
86
|
/** The time after which node transitions to SweepReady state; undefined if session expiry is disabled. */
|
|
84
|
-
sweepTimeoutMs
|
|
85
|
-
/** The current reference timestamp; undefined if no ops have ever been processed which can happen in tests. */
|
|
86
|
-
currentReferenceTimestampMs) {
|
|
87
|
+
sweepTimeoutMs) {
|
|
87
88
|
this.unreferencedTimestampMs = unreferencedTimestampMs;
|
|
88
89
|
this.inactiveTimeoutMs = inactiveTimeoutMs;
|
|
89
90
|
this.sweepTimeoutMs = sweepTimeoutMs;
|
|
90
|
-
this._state = UnreferencedState.Active;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (currentReferenceTimestampMs !== undefined) {
|
|
94
|
-
this.updateTracking(currentReferenceTimestampMs);
|
|
91
|
+
this._state = exports.UnreferencedState.Active;
|
|
92
|
+
if (this.sweepTimeoutMs !== undefined) {
|
|
93
|
+
(0, common_utils_1.assert)(this.inactiveTimeoutMs <= this.sweepTimeoutMs, 0x3b0 /* inactive timeout must not be greater than the sweep timeout */);
|
|
95
94
|
}
|
|
95
|
+
this.sweepTimer = new TimerWithNoDefaultTimeout(() => {
|
|
96
|
+
this._state = exports.UnreferencedState.SweepReady;
|
|
97
|
+
(0, common_utils_1.assert)(!this.inactiveTimer.hasTimer, 0x3b1 /* inactiveTimer still running after sweepTimer fired! */);
|
|
98
|
+
});
|
|
99
|
+
this.inactiveTimer = new TimerWithNoDefaultTimeout(() => {
|
|
100
|
+
this._state = exports.UnreferencedState.Inactive;
|
|
101
|
+
// After the node becomes inactive, start the sweep timer after which the node will be ready for sweep.
|
|
102
|
+
if (this.sweepTimeoutMs !== undefined) {
|
|
103
|
+
this.sweepTimer.restart(this.sweepTimeoutMs - this.inactiveTimeoutMs);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
this.updateTracking(currentReferenceTimestampMs);
|
|
96
107
|
}
|
|
97
108
|
get state() {
|
|
98
109
|
return this._state;
|
|
@@ -102,54 +113,45 @@ class UnreferencedStateTracker {
|
|
|
102
113
|
const unreferencedDurationMs = currentReferenceTimestampMs - this.unreferencedTimestampMs;
|
|
103
114
|
// If the node has been unreferenced for sweep timeout amount of time, update the state to SweepReady.
|
|
104
115
|
if (this.sweepTimeoutMs !== undefined && unreferencedDurationMs >= this.sweepTimeoutMs) {
|
|
105
|
-
this._state = UnreferencedState.SweepReady;
|
|
116
|
+
this._state = exports.UnreferencedState.SweepReady;
|
|
106
117
|
this.clearTimers();
|
|
107
118
|
return;
|
|
108
119
|
}
|
|
109
120
|
// If the node has been unreferenced for inactive timeoutMs amount of time, update the state to inactive.
|
|
110
121
|
// Also, start a timer for the sweep timeout.
|
|
111
122
|
if (unreferencedDurationMs >= this.inactiveTimeoutMs) {
|
|
112
|
-
this._state = UnreferencedState.Inactive;
|
|
113
|
-
this.
|
|
123
|
+
this._state = exports.UnreferencedState.Inactive;
|
|
124
|
+
this.inactiveTimer.clear();
|
|
114
125
|
if (this.sweepTimeoutMs !== undefined) {
|
|
115
|
-
|
|
126
|
+
this.sweepTimer.restart(this.sweepTimeoutMs - unreferencedDurationMs);
|
|
116
127
|
}
|
|
117
128
|
return;
|
|
118
129
|
}
|
|
119
|
-
// The node is still active.
|
|
120
|
-
|
|
121
|
-
if (this.inactiveTimer === undefined) {
|
|
122
|
-
const inactiveTimeoutHandler = () => {
|
|
123
|
-
this._state = UnreferencedState.Inactive;
|
|
124
|
-
// After the node becomes inactive, start the sweep timer after which the node will be ready for sweep.
|
|
125
|
-
if (this.sweepTimeoutMs !== undefined) {
|
|
126
|
-
setLongTimeout(this.sweepTimeoutMs - this.inactiveTimeoutMs, () => { this._state = UnreferencedState.SweepReady; }, (timer) => { this.sweepTimer = timer; });
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
this.inactiveTimer = new common_utils_1.Timer(remainingDurationMs, () => inactiveTimeoutHandler());
|
|
130
|
-
}
|
|
131
|
-
this.inactiveTimer.restart(remainingDurationMs);
|
|
130
|
+
// The node is still active. Ensure the inactive timer is running with the proper remaining duration.
|
|
131
|
+
this.inactiveTimer.restart(this.inactiveTimeoutMs - unreferencedDurationMs);
|
|
132
132
|
}
|
|
133
133
|
clearTimers() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (this.sweepTimer !== undefined) {
|
|
137
|
-
clearTimeout(this.sweepTimer);
|
|
138
|
-
}
|
|
134
|
+
this.inactiveTimer.clear();
|
|
135
|
+
this.sweepTimer.clear();
|
|
139
136
|
}
|
|
140
137
|
/** Stop tracking this node. Reset the unreferenced timers and state, if any. */
|
|
141
138
|
stopTracking() {
|
|
142
139
|
this.clearTimers();
|
|
143
|
-
this._state = UnreferencedState.Active;
|
|
140
|
+
this._state = exports.UnreferencedState.Active;
|
|
144
141
|
}
|
|
145
142
|
}
|
|
143
|
+
exports.UnreferencedStateTracker = UnreferencedStateTracker;
|
|
146
144
|
/**
|
|
147
145
|
* The garbage collector for the container runtime. It consolidates the garbage collection functionality and maintains
|
|
148
146
|
* its state across summaries.
|
|
149
147
|
*
|
|
150
148
|
* Node - represented as nodeId, it's a node on the GC graph
|
|
149
|
+
*
|
|
151
150
|
* Outbound Route - a path from one node to another node, think `nodeA` -\> `nodeB`
|
|
151
|
+
*
|
|
152
152
|
* Graph - all nodes with their respective routes
|
|
153
|
+
*
|
|
154
|
+
* ```
|
|
153
155
|
* GC Graph
|
|
154
156
|
*
|
|
155
157
|
* Node
|
|
@@ -159,20 +161,23 @@ class UnreferencedStateTracker {
|
|
|
159
161
|
* / \\
|
|
160
162
|
* Node Node
|
|
161
163
|
* NodeId = "dds1" NodeId = "dds2"
|
|
164
|
+
* ```
|
|
162
165
|
*/
|
|
163
166
|
class GarbageCollector {
|
|
164
167
|
constructor(createParams) {
|
|
165
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
168
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
166
169
|
/**
|
|
167
170
|
* Tells whether the GC data should be written to the root of the summary tree.
|
|
168
171
|
*/
|
|
169
172
|
this._writeDataAtRoot = true;
|
|
170
173
|
/**
|
|
171
174
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
175
|
+
*
|
|
172
176
|
* 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
|
|
173
|
-
*
|
|
177
|
+
* it was enabled before. GC state needs to be removed from summary and all nodes should be marked referenced.
|
|
178
|
+
*
|
|
174
179
|
* 2. The base snapshot does not have GC state but GC is enabled. This will happen the very first time GC runs on
|
|
175
|
-
*
|
|
180
|
+
* a document and the first time GC is enabled after is was disabled before.
|
|
176
181
|
*
|
|
177
182
|
* Note that the state needs reset only for the very first time summary is generated by this client. After that, the
|
|
178
183
|
* state will be up-to-date and this flag will be reset.
|
|
@@ -197,10 +202,12 @@ class GarbageCollector {
|
|
|
197
202
|
this.gcOptions = createParams.gcOptions;
|
|
198
203
|
this.getNodePackagePath = createParams.getNodePackagePath;
|
|
199
204
|
this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
|
|
205
|
+
this.activeConnection = createParams.activeConnection;
|
|
200
206
|
const baseSnapshot = createParams.baseSnapshot;
|
|
201
207
|
const metadata = createParams.metadata;
|
|
202
208
|
const readAndParseBlob = createParams.readAndParseBlob;
|
|
203
209
|
this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
|
|
210
|
+
this.sweepReadyUsageHandler = new gcSweepReadyUsageDetection_1.SweepReadyUsageDetectionHandler(createParams.getContainerDiagnosticId(), this.mc, this.runtime.closeFn);
|
|
204
211
|
let prevSummaryGCVersion;
|
|
205
212
|
/**
|
|
206
213
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
@@ -228,9 +235,9 @@ class GarbageCollector {
|
|
|
228
235
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
229
236
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
230
237
|
this.sweepEnabled = this.gcOptions.sweepAllowed === true;
|
|
231
|
-
// Set the Session Expiry only if the flag is enabled
|
|
238
|
+
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
232
239
|
if (this.mc.config.getBoolean(exports.runSessionExpiryKey) && this.gcEnabled) {
|
|
233
|
-
this.sessionExpiryTimeoutMs = exports.defaultSessionExpiryDurationMs;
|
|
240
|
+
this.sessionExpiryTimeoutMs = (_b = this.gcOptions.sessionExpiryTimeoutMs) !== null && _b !== void 0 ? _b : exports.defaultSessionExpiryDurationMs;
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
@@ -238,15 +245,20 @@ class GarbageCollector {
|
|
|
238
245
|
// If Test Override config is set, override Session Expiry timeout.
|
|
239
246
|
const overrideSessionExpiryTimeoutMs = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
240
247
|
const timeoutMs = overrideSessionExpiryTimeoutMs !== null && overrideSessionExpiryTimeoutMs !== void 0 ? overrideSessionExpiryTimeoutMs : this.sessionExpiryTimeoutMs;
|
|
241
|
-
|
|
248
|
+
this.sessionExpiryTimer = new common_utils_1.Timer(timeoutMs, () => { this.runtime.closeFn(new container_utils_1.ClientSessionExpiredError(`Client session expired.`, timeoutMs)); });
|
|
249
|
+
this.sessionExpiryTimer.start();
|
|
250
|
+
// TEMPORARY: Hardcode a default of 2 days which is the value used in the ODSP driver.
|
|
251
|
+
// This unblocks the Sweep Log (see logSweepEvents function).
|
|
252
|
+
// This will be removed before sweep is fully implemented.
|
|
253
|
+
const snapshotCacheExpiryMs = (_c = createParams.snapshotCacheExpiryMs) !== null && _c !== void 0 ? _c : 2 * 24 * 60 * 60 * 1000;
|
|
242
254
|
/**
|
|
243
255
|
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
244
256
|
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
|
|
245
257
|
* added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
|
|
246
258
|
* but make it one day to be safe.
|
|
247
259
|
*/
|
|
248
|
-
if (
|
|
249
|
-
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs +
|
|
260
|
+
if (snapshotCacheExpiryMs !== undefined) {
|
|
261
|
+
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + exports.oneDayMs;
|
|
250
262
|
}
|
|
251
263
|
}
|
|
252
264
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -254,36 +266,43 @@ class GarbageCollector {
|
|
|
254
266
|
this.latestSummaryGCVersion = prevSummaryGCVersion !== null && prevSummaryGCVersion !== void 0 ? prevSummaryGCVersion : this.currentGCVersion;
|
|
255
267
|
/**
|
|
256
268
|
* Whether GC should run or not. The following conditions have to be met to run sweep:
|
|
269
|
+
*
|
|
257
270
|
* 1. GC should be enabled for this container.
|
|
271
|
+
*
|
|
258
272
|
* 2. GC should not be disabled via disableGC GC option.
|
|
273
|
+
*
|
|
259
274
|
* These conditions can be overridden via runGCKey feature flag.
|
|
260
275
|
*/
|
|
261
|
-
this.shouldRunGC = (
|
|
276
|
+
this.shouldRunGC = (_d = this.mc.config.getBoolean(exports.runGCKey)) !== null && _d !== void 0 ? _d : (
|
|
262
277
|
// GC must be enabled for the document.
|
|
263
278
|
this.gcEnabled
|
|
264
279
|
// GC must not be disabled via GC options.
|
|
265
280
|
&& !this.gcOptions.disableGC);
|
|
266
281
|
/**
|
|
267
282
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
283
|
+
*
|
|
268
284
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
285
|
+
*
|
|
269
286
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
287
|
+
*
|
|
270
288
|
* 3. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
271
|
-
*
|
|
289
|
+
* feature flag.
|
|
272
290
|
*/
|
|
273
|
-
this.shouldRunSweep =
|
|
274
|
-
|
|
275
|
-
|
|
291
|
+
this.shouldRunSweep = false; // disable while TEMPORARY measure hardcoding snapshotCacheExpiryMs is here
|
|
292
|
+
// this.shouldRunGC
|
|
293
|
+
// && this.sweepTimeoutMs !== undefined
|
|
294
|
+
// && (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
|
|
276
295
|
this.trackGCState = this.mc.config.getBoolean(exports.trackGCStateKey) === true;
|
|
277
296
|
// Override inactive timeout if test config or gc options to override it is set.
|
|
278
|
-
this.inactiveTimeoutMs = (
|
|
297
|
+
this.inactiveTimeoutMs = (_f = (_e = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _e !== void 0 ? _e : this.gcOptions.inactiveTimeoutMs) !== null && _f !== void 0 ? _f : exports.defaultInactiveTimeoutMs;
|
|
279
298
|
// Inactive timeout must be greater than sweep timeout since a node goes from active -> inactive -> sweep ready.
|
|
280
299
|
if (this.sweepTimeoutMs !== undefined && this.inactiveTimeoutMs > this.sweepTimeoutMs) {
|
|
281
|
-
throw new container_utils_1.UsageError("inactive timeout should not be
|
|
300
|
+
throw new container_utils_1.UsageError("inactive timeout should not be greater than the sweep timeout");
|
|
282
301
|
}
|
|
283
302
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
284
|
-
this.testMode = (
|
|
303
|
+
this.testMode = (_g = this.mc.config.getBoolean(exports.gcTestModeKey)) !== null && _g !== void 0 ? _g : this.gcOptions.runGCInTestMode === true;
|
|
285
304
|
// GC state is written into root of the summary tree by default. Can be overridden via feature flag for now.
|
|
286
|
-
this._writeDataAtRoot = (
|
|
305
|
+
this._writeDataAtRoot = (_h = this.mc.config.getBoolean(writeAtRootKey)) !== null && _h !== void 0 ? _h : true;
|
|
287
306
|
if (this._writeDataAtRoot) {
|
|
288
307
|
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
289
308
|
// contain GC tree and GC is enabled.
|
|
@@ -297,52 +316,59 @@ class GarbageCollector {
|
|
|
297
316
|
if (baseSnapshot === undefined) {
|
|
298
317
|
return undefined;
|
|
299
318
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
312
|
-
// consolidate into IGarbageCollectionState format.
|
|
313
|
-
// Add a node for the root node that is not present in older snapshot format.
|
|
314
|
-
const gcState = { gcNodes: { "/": { outboundRoutes: [] } } };
|
|
315
|
-
const dataStoreSnapshotTree = (0, dataStores_1.getSummaryForDatastores)(baseSnapshot, metadata);
|
|
316
|
-
(0, common_utils_1.assert)(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
317
|
-
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
318
|
-
const blobId = dsSnapshotTree.blobs[runtime_definitions_1.gcBlobKey];
|
|
319
|
-
if (blobId === undefined) {
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
const gcSummaryDetails = await readAndParseBlob(blobId);
|
|
323
|
-
// If there are no nodes for this data store, skip it.
|
|
324
|
-
if (((_a = gcSummaryDetails.gcData) === null || _a === void 0 ? void 0 : _a.gcNodes) === undefined) {
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
const dsRootId = `/${dsId}`;
|
|
328
|
-
// Since we used to write GC data at data store level, we won't have an entry for the root ("/").
|
|
329
|
-
// Construct that entry by adding root data store ids to its outbound routes.
|
|
330
|
-
const initialSnapshotDetails = await readAndParseBlob(dsSnapshotTree.blobs[summaryFormat_1.dataStoreAttributesBlobName]);
|
|
331
|
-
if (initialSnapshotDetails.isRootDataStore) {
|
|
332
|
-
gcState.gcNodes["/"].outboundRoutes.push(dsRootId);
|
|
319
|
+
try {
|
|
320
|
+
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
321
|
+
const gcSnapshotTree = baseSnapshot.trees[exports.gcTreeKey];
|
|
322
|
+
if (gcSnapshotTree !== undefined) {
|
|
323
|
+
// If the GC tree is written at root, we should also do the same.
|
|
324
|
+
this._writeDataAtRoot = true;
|
|
325
|
+
const baseGCState = await getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
|
|
326
|
+
if (this.trackGCState) {
|
|
327
|
+
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
328
|
+
}
|
|
329
|
+
return baseGCState;
|
|
333
330
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
332
|
+
// consolidate into IGarbageCollectionState format.
|
|
333
|
+
// Add a node for the root node that is not present in older snapshot format.
|
|
334
|
+
const gcState = { gcNodes: { "/": { outboundRoutes: [] } } };
|
|
335
|
+
const dataStoreSnapshotTree = (0, dataStores_1.getSummaryForDatastores)(baseSnapshot, metadata);
|
|
336
|
+
(0, common_utils_1.assert)(dataStoreSnapshotTree !== undefined, 0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
|
|
337
|
+
for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
|
|
338
|
+
const blobId = dsSnapshotTree.blobs[runtime_definitions_1.gcBlobKey];
|
|
339
|
+
if (blobId === undefined) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const gcSummaryDetails = await readAndParseBlob(blobId);
|
|
343
|
+
// If there are no nodes for this data store, skip it.
|
|
344
|
+
if (((_a = gcSummaryDetails.gcData) === null || _a === void 0 ? void 0 : _a.gcNodes) === undefined) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const dsRootId = `/${dsId}`;
|
|
348
|
+
// Since we used to write GC data at data store level, we won't have an entry for the root ("/").
|
|
349
|
+
// Construct that entry by adding root data store ids to its outbound routes.
|
|
350
|
+
const initialSnapshotDetails = await readAndParseBlob(dsSnapshotTree.blobs[summaryFormat_1.dataStoreAttributesBlobName]);
|
|
351
|
+
if (initialSnapshotDetails.isRootDataStore) {
|
|
352
|
+
gcState.gcNodes["/"].outboundRoutes.push(dsRootId);
|
|
353
|
+
}
|
|
354
|
+
for (const [id, outboundRoutes] of Object.entries(gcSummaryDetails.gcData.gcNodes)) {
|
|
355
|
+
// Prefix the data store id to the GC node ids to make them relative to the root from being
|
|
356
|
+
// relative to the data store. Similar to how its done in DataStore::getGCData.
|
|
357
|
+
const rootId = id === "/" ? dsRootId : `${dsRootId}${id}`;
|
|
358
|
+
gcState.gcNodes[rootId] = { outboundRoutes: Array.from(outboundRoutes) };
|
|
359
|
+
}
|
|
360
|
+
(0, common_utils_1.assert)(gcState.gcNodes[dsRootId] !== undefined, 0x2a9 /* GC nodes for data store not in GC blob */);
|
|
361
|
+
gcState.gcNodes[dsRootId].unreferencedTimestampMs = gcSummaryDetails.unrefTimestamp;
|
|
339
362
|
}
|
|
340
|
-
|
|
341
|
-
|
|
363
|
+
// If there is only one node (root node just added above), either GC is disabled or we are loading from
|
|
364
|
+
// the first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
365
|
+
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
const dpe = container_utils_1.DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
369
|
+
dpe.addTelemetryProperties({ gcConfigs: JSON.stringify(this.configs) });
|
|
370
|
+
throw dpe;
|
|
342
371
|
}
|
|
343
|
-
// If there is only one node (root node just added above), either GC is disabled or we are loading from the
|
|
344
|
-
// very first summary generated by detached container. In both cases, GC was not run - return undefined.
|
|
345
|
-
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
346
372
|
});
|
|
347
373
|
/**
|
|
348
374
|
* Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
|
|
@@ -351,14 +377,36 @@ class GarbageCollector {
|
|
|
351
377
|
*/
|
|
352
378
|
this.initializeBaseStateP = new common_utils_1.LazyPromise(async () => {
|
|
353
379
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
380
|
+
/**
|
|
381
|
+
* If there is no current reference timestamp, skip initialization. We need the current timestamp to track
|
|
382
|
+
* how long objects have been unreferenced and if they can be deleted.
|
|
383
|
+
*
|
|
384
|
+
* Note that the only scenario where there is no reference timestamp is when no ops have ever been processed
|
|
385
|
+
* for this container and it is in read mode. In this scenario, there is no point in running GC anyway
|
|
386
|
+
* because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
387
|
+
*/
|
|
388
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
389
|
+
// Log an event so we can evaluate how often we run into this scenario.
|
|
390
|
+
this.mc.logger.sendErrorEvent({
|
|
391
|
+
eventName: "GarbageCollectorInitializedWithoutTimestamp",
|
|
392
|
+
gcConfigs: JSON.stringify(this.configs),
|
|
393
|
+
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
354
396
|
const baseState = await baseSummaryStateP;
|
|
397
|
+
/**
|
|
398
|
+
* The base state will not be present if the container is loaded from:
|
|
399
|
+
* 1. The first summary created by the detached container.
|
|
400
|
+
* 2. A summary that was generated with GC disabled.
|
|
401
|
+
* 3. A summary that was generated before GC even existed.
|
|
402
|
+
*/
|
|
355
403
|
if (baseState === undefined) {
|
|
356
404
|
return;
|
|
357
405
|
}
|
|
358
406
|
const gcNodes = {};
|
|
359
407
|
for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
|
|
360
408
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
361
|
-
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, this.sweepTimeoutMs
|
|
409
|
+
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(nodeData.unreferencedTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
362
410
|
}
|
|
363
411
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
364
412
|
}
|
|
@@ -380,7 +428,7 @@ class GarbageCollector {
|
|
|
380
428
|
// each node in the summary.
|
|
381
429
|
const usedRoutes = (0, garbage_collector_1.runGarbageCollection)(gcNodes, ["/"]).referencedNodeIds;
|
|
382
430
|
const baseGCDetailsMap = (0, garbage_collector_1.unpackChildNodesGCDetails)({ gcData: { gcNodes }, usedRoutes });
|
|
383
|
-
// Currently, the nodes may write the GC data. So, we need to update
|
|
431
|
+
// Currently, the nodes may write the GC data. So, we need to update its base GC details with the
|
|
384
432
|
// unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
|
|
385
433
|
for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
|
|
386
434
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
@@ -394,19 +442,10 @@ class GarbageCollector {
|
|
|
394
442
|
});
|
|
395
443
|
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
396
444
|
// summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
|
|
397
|
-
const gcConfigProps = JSON.stringify(Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, writeAtRoot: this._writeDataAtRoot, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, existing: createParams.existing, trackGCState: this.trackGCState }, this.gcOptions));
|
|
398
445
|
if (this.isSummarizerClient) {
|
|
399
446
|
this.mc.logger.sendTelemetryEvent({
|
|
400
447
|
eventName: "GarbageCollectorLoaded",
|
|
401
|
-
gcConfigs:
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
// Initialize the base state that is used to detect when inactive objects are used.
|
|
405
|
-
if (this.shouldRunGC) {
|
|
406
|
-
this.initializeBaseStateP.catch((error) => {
|
|
407
|
-
const dpe = container_utils_1.DataProcessingError.wrapIfUnrecognized(error, "FailedToInitializeGC");
|
|
408
|
-
dpe.addTelemetryProperties({ gcConfigs: gcConfigProps });
|
|
409
|
-
throw dpe;
|
|
448
|
+
gcConfigs: JSON.stringify(this.configs),
|
|
410
449
|
});
|
|
411
450
|
}
|
|
412
451
|
}
|
|
@@ -415,11 +454,16 @@ class GarbageCollector {
|
|
|
415
454
|
}
|
|
416
455
|
/**
|
|
417
456
|
* Tells whether the GC state needs to be reset in the next summary. We need to do this if:
|
|
457
|
+
*
|
|
418
458
|
* 1. GC was enabled and is now disabled. The GC state needs to be removed and everything becomes referenced.
|
|
459
|
+
*
|
|
419
460
|
* 2. GC was disabled and is now enabled. The GC state needs to be regenerated and added to summary.
|
|
461
|
+
*
|
|
420
462
|
* 3. The GC version in the latest summary is different from the current GC version. This can happen if:
|
|
421
|
-
*
|
|
422
|
-
*
|
|
463
|
+
*
|
|
464
|
+
* 3.1. The summary this client loaded with has data from a different GC version.
|
|
465
|
+
*
|
|
466
|
+
* 3.2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
423
467
|
*/
|
|
424
468
|
get summaryStateNeedsReset() {
|
|
425
469
|
return this.initialStateNeedsReset ||
|
|
@@ -428,22 +472,64 @@ class GarbageCollector {
|
|
|
428
472
|
get writeDataAtRoot() {
|
|
429
473
|
return this._writeDataAtRoot;
|
|
430
474
|
}
|
|
475
|
+
/** Returns a list of all the configurations for garbage collection. */
|
|
476
|
+
get configs() {
|
|
477
|
+
return Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, writeAtRoot: this._writeDataAtRoot, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, trackGCState: this.trackGCState }, this.gcOptions);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
|
|
481
|
+
* to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
|
|
482
|
+
* @param connected - Whether the runtime connected / disconnected.
|
|
483
|
+
* @param clientId - The clientId of this runtime.
|
|
484
|
+
*/
|
|
485
|
+
setConnectionState(connected, clientId) {
|
|
486
|
+
/**
|
|
487
|
+
* For non-summarizer clients, initialize the base state when the container becomes active, i.e., it transitions
|
|
488
|
+
* to "write" mode. This will ensure that the container's own join op is processed and there is a recent
|
|
489
|
+
* reference timestamp that will be used to update the state of unreferenced nodes. Also, all trailing ops which
|
|
490
|
+
* could affect the GC state will have been processed.
|
|
491
|
+
*
|
|
492
|
+
* Ideally, this initialization should only be done for summarizer client. However, we are currently rolling out
|
|
493
|
+
* sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
|
|
494
|
+
*/
|
|
495
|
+
if (this.activeConnection() && !this.isSummarizerClient && this.shouldRunGC) {
|
|
496
|
+
this.initializeBaseStateP.catch((error) => { });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
431
499
|
/**
|
|
432
500
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
433
|
-
* @returns the
|
|
501
|
+
* @returns stats of the GC run or undefined if GC did not run.
|
|
434
502
|
*/
|
|
435
503
|
async collectGarbage(options) {
|
|
436
|
-
|
|
504
|
+
var _a;
|
|
505
|
+
const fullGC = (_a = options.fullGC) !== null && _a !== void 0 ? _a : (this.gcOptions.runFullGC === true || this.summaryStateNeedsReset);
|
|
437
506
|
const logger = options.logger
|
|
438
507
|
? telemetry_utils_1.ChildLogger.create(options.logger, undefined, { all: { completedGCRuns: () => this.completedRuns } })
|
|
439
508
|
: this.mc.logger;
|
|
509
|
+
/**
|
|
510
|
+
* If there is no current reference timestamp, skip running GC. We need the current timestamp to track
|
|
511
|
+
* how long objects have been unreferenced and if they should be deleted.
|
|
512
|
+
*
|
|
513
|
+
* Note that the only scenario where GC is called and there is no reference timestamp is when no ops have ever
|
|
514
|
+
* been processed for this container and it is in read mode. In this scenario, there is no point in running GC
|
|
515
|
+
* anyway because references in the container do not change without any ops, i.e., there is nothing to collect.
|
|
516
|
+
*/
|
|
517
|
+
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
518
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
519
|
+
// Log an event so we can evaluate how often we run into this scenario.
|
|
520
|
+
logger.sendErrorEvent({
|
|
521
|
+
eventName: "CollectGarbageCalledWithoutTimestamp",
|
|
522
|
+
gcConfigs: JSON.stringify(this.configs),
|
|
523
|
+
});
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
440
526
|
return telemetry_utils_1.PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
|
|
441
527
|
await this.runPreGCSteps();
|
|
442
528
|
// Get the runtime's GC data and run GC on the reference graph in it.
|
|
443
529
|
const gcData = await this.runtime.getGCData(fullGC);
|
|
444
530
|
const gcResult = (0, garbage_collector_1.runGarbageCollection)(gcData.gcNodes, ["/"]);
|
|
445
|
-
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger);
|
|
446
|
-
event.end(Object.assign({}, gcStats));
|
|
531
|
+
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
532
|
+
event.end(Object.assign(Object.assign({}, gcStats), { timestamp: currentReferenceTimestampMs }));
|
|
447
533
|
this.completedRuns++;
|
|
448
534
|
return gcStats;
|
|
449
535
|
}, { end: true, cancel: "error" });
|
|
@@ -454,7 +540,7 @@ class GarbageCollector {
|
|
|
454
540
|
// Let the runtime update its pending state before GC runs.
|
|
455
541
|
await this.runtime.updateStateBeforeGC();
|
|
456
542
|
}
|
|
457
|
-
async runPostGCSteps(gcData, gcResult, logger) {
|
|
543
|
+
async runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs) {
|
|
458
544
|
// Generate statistics from the current run. This is done before updating the current state because it
|
|
459
545
|
// generates some of its data based on previous state of the system.
|
|
460
546
|
const gcStats = this.generateStats(gcResult);
|
|
@@ -462,7 +548,6 @@ class GarbageCollector {
|
|
|
462
548
|
// the current run. We need to identify than and update their unreferenced state if needed.
|
|
463
549
|
this.updateStateSinceLastRun(gcData, logger);
|
|
464
550
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
465
|
-
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
466
551
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
467
552
|
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
|
|
468
553
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
@@ -591,7 +676,7 @@ class GarbageCollector {
|
|
|
591
676
|
return;
|
|
592
677
|
}
|
|
593
678
|
const nodeStateTracker = this.unreferencedNodesState.get(nodePath);
|
|
594
|
-
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
679
|
+
if (nodeStateTracker && nodeStateTracker.state !== exports.UnreferencedState.Active) {
|
|
595
680
|
this.inactiveNodeUsed(reason, nodePath, nodeStateTracker, undefined /* fromNodeId */, packagePath, timestampMs, requestHeaders);
|
|
596
681
|
}
|
|
597
682
|
}
|
|
@@ -611,15 +696,14 @@ class GarbageCollector {
|
|
|
611
696
|
outboundRoutes.push(toNodePath);
|
|
612
697
|
this.newReferencesSinceLastRun.set(fromNodePath, outboundRoutes);
|
|
613
698
|
const nodeStateTracker = this.unreferencedNodesState.get(toNodePath);
|
|
614
|
-
if (nodeStateTracker && nodeStateTracker.state !== UnreferencedState.Active) {
|
|
699
|
+
if (nodeStateTracker && nodeStateTracker.state !== exports.UnreferencedState.Active) {
|
|
615
700
|
this.inactiveNodeUsed("Revived", toNodePath, nodeStateTracker, fromNodePath);
|
|
616
701
|
}
|
|
617
702
|
}
|
|
618
703
|
dispose() {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
704
|
+
var _a;
|
|
705
|
+
(_a = this.sessionExpiryTimer) === null || _a === void 0 ? void 0 : _a.clear();
|
|
706
|
+
this.sessionExpiryTimer = undefined;
|
|
623
707
|
}
|
|
624
708
|
/**
|
|
625
709
|
* Updates the state of the system as per the current GC run. It does the following:
|
|
@@ -643,14 +727,6 @@ class GarbageCollector {
|
|
|
643
727
|
this.unreferencedNodesState.delete(nodeId);
|
|
644
728
|
}
|
|
645
729
|
}
|
|
646
|
-
/**
|
|
647
|
-
* If there is no current reference time, skip tracking when a node becomes unreferenced. This would happen
|
|
648
|
-
* if no ops have been processed ever and we still try to run GC. If so, there is nothing interesting to track
|
|
649
|
-
* anyway.
|
|
650
|
-
*/
|
|
651
|
-
if (currentReferenceTimestampMs === undefined) {
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
730
|
/**
|
|
655
731
|
* If a node became unreferenced in this run, start tracking it.
|
|
656
732
|
* If a node was already unreferenced, update its tracking information. Since the current reference time is
|
|
@@ -659,7 +735,7 @@ class GarbageCollector {
|
|
|
659
735
|
for (const nodeId of gcResult.deletedNodeIds) {
|
|
660
736
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
661
737
|
if (nodeStateTracker === undefined) {
|
|
662
|
-
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(currentReferenceTimestampMs, this.inactiveTimeoutMs, this.sweepTimeoutMs
|
|
738
|
+
this.unreferencedNodesState.set(nodeId, new UnreferencedStateTracker(currentReferenceTimestampMs, this.inactiveTimeoutMs, currentReferenceTimestampMs, this.sweepTimeoutMs));
|
|
663
739
|
}
|
|
664
740
|
else {
|
|
665
741
|
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
@@ -702,14 +778,19 @@ class GarbageCollector {
|
|
|
702
778
|
* run, and then add the references since last run.
|
|
703
779
|
*
|
|
704
780
|
* Note on why we need to combine the data from previous run, current run and all references in between -
|
|
781
|
+
*
|
|
705
782
|
* 1. We need data from last run because some of its references may have been deleted since then. If those
|
|
706
|
-
*
|
|
783
|
+
* references added new outbound references before getting deleted, we need to detect them.
|
|
784
|
+
*
|
|
707
785
|
* 2. We need new outbound references since last run because some of them may have been deleted later. If those
|
|
708
|
-
*
|
|
786
|
+
* references added new outbound references before getting deleted, we need to detect them.
|
|
787
|
+
*
|
|
709
788
|
* 3. We need data from the current run because currently we may not detect when DDSes are referenced:
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
789
|
+
*
|
|
790
|
+
* - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
|
|
791
|
+
* which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
|
|
792
|
+
*
|
|
793
|
+
* - A new data store may have "root" DDSes already created and we don't detect them today.
|
|
713
794
|
*/
|
|
714
795
|
const gcDataSuperSet = (0, garbage_collector_1.concatGarbageCollectionData)(this.previousGCDataFromLastRun, currentGCData);
|
|
715
796
|
this.newReferencesSinceLastRun.forEach((outboundRoutes, sourceNodeId) => {
|
|
@@ -837,13 +918,11 @@ class GarbageCollector {
|
|
|
837
918
|
* this will give us a view into how much deleted content a container has.
|
|
838
919
|
*/
|
|
839
920
|
logSweepEvents(logger, currentReferenceTimestampMs) {
|
|
840
|
-
if (this.mc.config.getBoolean(disableSweepLogKey) === true
|
|
841
|
-
|| currentReferenceTimestampMs === undefined
|
|
842
|
-
|| this.sweepTimeoutMs === undefined) {
|
|
921
|
+
if (this.mc.config.getBoolean(exports.disableSweepLogKey) === true || this.sweepTimeoutMs === undefined) {
|
|
843
922
|
return;
|
|
844
923
|
}
|
|
845
924
|
this.unreferencedNodesState.forEach((nodeStateTracker, nodeId) => {
|
|
846
|
-
if (nodeStateTracker.state !== UnreferencedState.SweepReady) {
|
|
925
|
+
if (nodeStateTracker.state !== exports.UnreferencedState.SweepReady) {
|
|
847
926
|
return;
|
|
848
927
|
}
|
|
849
928
|
const nodeType = this.runtime.getNodeType(nodeId);
|
|
@@ -874,12 +953,7 @@ class GarbageCollector {
|
|
|
874
953
|
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
875
954
|
// logging as nothing interesting would have happened worth logging.
|
|
876
955
|
// If the node is active, skip logging.
|
|
877
|
-
if (currentReferenceTimestampMs === undefined || nodeStateTracker.state === UnreferencedState.Active) {
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
881
|
-
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects).
|
|
882
|
-
if (!this.isSummarizerClient && usageType !== "Loaded") {
|
|
956
|
+
if (currentReferenceTimestampMs === undefined || nodeStateTracker.state === exports.UnreferencedState.Active) {
|
|
883
957
|
return;
|
|
884
958
|
}
|
|
885
959
|
// We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
|
|
@@ -899,7 +973,7 @@ class GarbageCollector {
|
|
|
899
973
|
type: nodeType,
|
|
900
974
|
unrefTime: nodeStateTracker.unreferencedTimestampMs,
|
|
901
975
|
age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
|
|
902
|
-
timeout: nodeStateTracker.state === UnreferencedState.Inactive
|
|
976
|
+
timeout: nodeStateTracker.state === exports.UnreferencedState.Inactive
|
|
903
977
|
? this.inactiveTimeoutMs
|
|
904
978
|
: this.sweepTimeoutMs,
|
|
905
979
|
completedGCRuns: this.completedRuns,
|
|
@@ -915,7 +989,17 @@ class GarbageCollector {
|
|
|
915
989
|
this.pendingEventsQueue.push(Object.assign(Object.assign({}, propsToLog), { usageType, state }));
|
|
916
990
|
}
|
|
917
991
|
else {
|
|
918
|
-
|
|
992
|
+
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
993
|
+
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
|
|
994
|
+
if (usageType === "Loaded") {
|
|
995
|
+
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, propsToLog), { eventName: `${state}Object_${usageType}`, pkg: packagePath ? { value: packagePath.join("/"), tag: telemetry_utils_1.TelemetryDataTag.CodeArtifact } : undefined }));
|
|
996
|
+
}
|
|
997
|
+
// If SweepReady Usage Detection is enabed, the handler may close the interactive container.
|
|
998
|
+
// Once Sweep is fully implemented, this will be removed since the objects will be gone
|
|
999
|
+
// and errors will arise elsewhere in the runtime
|
|
1000
|
+
if (state === exports.UnreferencedState.SweepReady) {
|
|
1001
|
+
this.sweepReadyUsageHandler.usageDetectedInInteractiveClient(Object.assign(Object.assign({}, propsToLog), { usageType }));
|
|
1002
|
+
}
|
|
919
1003
|
}
|
|
920
1004
|
}
|
|
921
1005
|
async logUnreferencedEvents(logger) {
|
|
@@ -928,7 +1012,7 @@ class GarbageCollector {
|
|
|
928
1012
|
* revived and a Revived event will be logged for it.
|
|
929
1013
|
*/
|
|
930
1014
|
const nodeStateTracker = this.unreferencedNodesState.get(eventProps.id);
|
|
931
|
-
const active = nodeStateTracker === undefined || nodeStateTracker.state === UnreferencedState.Active;
|
|
1015
|
+
const active = nodeStateTracker === undefined || nodeStateTracker.state === exports.UnreferencedState.Active;
|
|
932
1016
|
if ((usageType === "Revived") === active) {
|
|
933
1017
|
const pkg = await this.getNodePackagePath(eventProps.id);
|
|
934
1018
|
const fromPkg = eventProps.fromId ? await this.getNodePackagePath(eventProps.fromId) : undefined;
|
|
@@ -971,23 +1055,18 @@ function generateSortedGCState(gcState) {
|
|
|
971
1055
|
}
|
|
972
1056
|
return sortedGCState;
|
|
973
1057
|
}
|
|
974
|
-
/**
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
timer = setTimeout(() => timeoutFn(), timeoutMs);
|
|
990
|
-
}
|
|
991
|
-
setTimerFn(timer);
|
|
1058
|
+
/** A wrapper around common-utils Timer that requires the timeout when calling start/restart */
|
|
1059
|
+
class TimerWithNoDefaultTimeout extends common_utils_1.Timer {
|
|
1060
|
+
constructor(callback) {
|
|
1061
|
+
// The default timeout/handlers will never be used since start/restart pass overrides below
|
|
1062
|
+
super(0, () => { throw new Error("DefaultHandler should not be used"); });
|
|
1063
|
+
this.callback = callback;
|
|
1064
|
+
}
|
|
1065
|
+
start(timeoutMs) {
|
|
1066
|
+
super.start(timeoutMs, this.callback);
|
|
1067
|
+
}
|
|
1068
|
+
restart(timeoutMs) {
|
|
1069
|
+
super.restart(timeoutMs, this.callback);
|
|
1070
|
+
}
|
|
992
1071
|
}
|
|
993
1072
|
//# sourceMappingURL=garbageCollection.js.map
|