@fluidframework/container-runtime 0.56.0 → 0.57.0-51086
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/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +9 -1
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +6 -6
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +65 -25
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +149 -79
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +62 -0
- package/dist/dataStore.d.ts.map +1 -0
- package/dist/dataStore.js +135 -0
- package/dist/dataStore.js.map +1 -0
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +9 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +14 -19
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +47 -21
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +195 -61
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.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/runningSummarizer.d.ts +1 -0
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +23 -15
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +6 -6
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryGenerator.d.ts +2 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +46 -28
- package/dist/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +9 -1
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +6 -6
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +65 -25
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +150 -80
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +62 -0
- package/lib/dataStore.d.ts.map +1 -0
- package/lib/dataStore.js +130 -0
- package/lib/dataStore.js.map +1 -0
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +9 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +13 -18
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +47 -21
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +197 -63
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.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/runningSummarizer.d.ts +1 -0
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +23 -15
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +6 -6
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryGenerator.d.ts +2 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +46 -28
- package/lib/summaryGenerator.js.map +1 -1
- package/package.json +13 -13
- package/src/blobManager.ts +12 -1
- package/src/connectionTelemetry.ts +7 -6
- package/src/containerRuntime.ts +231 -103
- package/src/dataStore.ts +187 -0
- package/src/dataStoreContext.ts +1 -1
- package/src/dataStores.ts +18 -38
- package/src/garbageCollection.ts +283 -105
- package/src/index.ts +3 -1
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +25 -16
- package/src/summarizerTypes.ts +6 -8
- package/src/summaryGenerator.ts +72 -23
|
@@ -11,6 +11,7 @@ const garbage_collector_1 = require("@fluidframework/garbage-collector");
|
|
|
11
11
|
const runtime_definitions_1 = require("@fluidframework/runtime-definitions");
|
|
12
12
|
const runtime_utils_1 = require("@fluidframework/runtime-utils");
|
|
13
13
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
14
|
+
const _1 = require(".");
|
|
14
15
|
const dataStores_1 = require("./dataStores");
|
|
15
16
|
const summaryFormat_1 = require("./summaryFormat");
|
|
16
17
|
/** This is the current version of garbage collection. */
|
|
@@ -27,7 +28,11 @@ const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
|
27
28
|
const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
28
29
|
// Feature gate key to write GC data at the root of the summary tree.
|
|
29
30
|
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
31
|
+
// Feature gate key to expire a session after a set period of time.
|
|
32
|
+
const runSessionExpiry = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
30
33
|
const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
34
|
+
const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
35
|
+
;
|
|
31
36
|
/**
|
|
32
37
|
* Helper class that tracks the state of an unreferenced node such as the time it was unreferenced. It also sets
|
|
33
38
|
* the node's state to inactive if it remains unreferenced for a given amount of time (inactiveTimeoutMs).
|
|
@@ -35,37 +40,25 @@ const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
|
35
40
|
class UnreferencedStateTracker {
|
|
36
41
|
constructor(unreferencedTimestampMs, inactiveTimeoutMs) {
|
|
37
42
|
this.unreferencedTimestampMs = unreferencedTimestampMs;
|
|
38
|
-
this.
|
|
39
|
-
// Keeps track of all inactive events that are logged. This is used to limit the log generation for each event to 1
|
|
40
|
-
// so that it is not noisy.
|
|
41
|
-
this.inactiveEventsLogged = new Set();
|
|
43
|
+
this._inactive = false;
|
|
42
44
|
// If the timeout has already expired, the node should become inactive immediately. Otherwise, start a timer of
|
|
43
45
|
// inactiveTimeoutMs after which the node will become inactive.
|
|
44
46
|
if (inactiveTimeoutMs <= 0) {
|
|
45
|
-
this.
|
|
47
|
+
this._inactive = true;
|
|
46
48
|
}
|
|
47
49
|
else {
|
|
48
|
-
this.timer = new common_utils_1.Timer(inactiveTimeoutMs, () => { this.
|
|
50
|
+
this.timer = new common_utils_1.Timer(inactiveTimeoutMs, () => { this._inactive = true; });
|
|
49
51
|
this.timer.start();
|
|
50
52
|
}
|
|
51
53
|
}
|
|
54
|
+
get inactive() {
|
|
55
|
+
return this._inactive;
|
|
56
|
+
}
|
|
52
57
|
/** Stop tracking this node. Reset the unreferenced timer, if any, and reset inactive state. */
|
|
53
58
|
stopTracking() {
|
|
54
59
|
var _a;
|
|
55
60
|
(_a = this.timer) === null || _a === void 0 ? void 0 : _a.clear();
|
|
56
|
-
this.
|
|
57
|
-
}
|
|
58
|
-
/** Logs an error with the given properties if the node is inactive. */
|
|
59
|
-
logIfInactive(logger, eventName, currentTimestampMs, deleteTimeoutMs, inactiveNodeId) {
|
|
60
|
-
if (this.inactive && !this.inactiveEventsLogged.has(eventName)) {
|
|
61
|
-
logger.sendErrorEvent({
|
|
62
|
-
eventName,
|
|
63
|
-
age: currentTimestampMs - this.unreferencedTimestampMs,
|
|
64
|
-
timeout: deleteTimeoutMs,
|
|
65
|
-
id: inactiveNodeId,
|
|
66
|
-
});
|
|
67
|
-
this.inactiveEventsLogged.add(eventName);
|
|
68
|
-
}
|
|
61
|
+
this._inactive = false;
|
|
69
62
|
}
|
|
70
63
|
}
|
|
71
64
|
/**
|
|
@@ -76,12 +69,15 @@ class GarbageCollector {
|
|
|
76
69
|
constructor(provider, gcOptions,
|
|
77
70
|
/** After GC has run, called to delete objects in the runtime whose routes are unused. */
|
|
78
71
|
deleteUnusedRoutes,
|
|
72
|
+
/** For a given node path, returns the node's package path. */
|
|
73
|
+
getNodePackagePath,
|
|
79
74
|
/** Returns the current timestamp to be assigned to nodes that become unreferenced. */
|
|
80
75
|
getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
|
|
81
76
|
var _a, _b, _c, _d, _e;
|
|
82
77
|
this.provider = provider;
|
|
83
78
|
this.gcOptions = gcOptions;
|
|
84
79
|
this.deleteUnusedRoutes = deleteUnusedRoutes;
|
|
80
|
+
this.getNodePackagePath = getNodePackagePath;
|
|
85
81
|
this.getCurrentTimestampMs = getCurrentTimestampMs;
|
|
86
82
|
this.closeFn = closeFn;
|
|
87
83
|
/**
|
|
@@ -106,6 +102,11 @@ class GarbageCollector {
|
|
|
106
102
|
this.referencesSinceLastRun = new Map();
|
|
107
103
|
// Map of node ids to their unreferenced state tracker.
|
|
108
104
|
this.unreferencedNodesState = new Map();
|
|
105
|
+
// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
|
|
106
|
+
// per event per node.
|
|
107
|
+
this.loggedUnreferencedEvents = new Set();
|
|
108
|
+
// Queue for unreferenced events that should be logged the next time GC runs.
|
|
109
|
+
this.pendingEventsQueue = [];
|
|
109
110
|
this.mc = telemetry_utils_1.loggerToMonitoringContext(telemetry_utils_1.ChildLogger.create(baseLogger, "GarbageCollector"));
|
|
110
111
|
this.deleteTimeoutMs = (_a = this.gcOptions.deleteTimeoutMs) !== null && _a !== void 0 ? _a : defaultDeleteTimeoutMs;
|
|
111
112
|
let prevSummaryGCVersion;
|
|
@@ -122,12 +123,19 @@ class GarbageCollector {
|
|
|
122
123
|
else {
|
|
123
124
|
// For new documents, GC has to be exlicitly enabled via the gcAllowed flag in GC options.
|
|
124
125
|
this.gcEnabled = gcOptions.gcAllowed === true;
|
|
125
|
-
|
|
126
|
+
// Set the Session Expiry only if the flag is enabled or the test option is set.
|
|
127
|
+
if (this.mc.config.getBoolean(runSessionExpiry) && this.gcEnabled) {
|
|
128
|
+
this.sessionExpiryTimeoutMs = defaultSessionExpiryDurationMs;
|
|
129
|
+
}
|
|
126
130
|
}
|
|
127
131
|
// If session expiry is enabled, we need to close the container when the timeout expires
|
|
128
132
|
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
129
|
-
const
|
|
130
|
-
|
|
133
|
+
const timeoutMs = this.sessionExpiryTimeoutMs;
|
|
134
|
+
setLongTimeout(timeoutMs, () => {
|
|
135
|
+
this.closeFn(new container_utils_1.ClientSessionExpiredError(`Client session expired.`, timeoutMs));
|
|
136
|
+
}, (timer) => {
|
|
137
|
+
this.sessionExpiryTimer = timer;
|
|
138
|
+
});
|
|
131
139
|
}
|
|
132
140
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
133
141
|
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
@@ -256,9 +264,22 @@ class GarbageCollector {
|
|
|
256
264
|
}
|
|
257
265
|
return dataStoreGCDetailsMap;
|
|
258
266
|
});
|
|
267
|
+
// Initialize the base state. The base GC data is used to detect and log when inactive / deleted objects are
|
|
268
|
+
// used in the container.
|
|
269
|
+
if (this.shouldRunGC) {
|
|
270
|
+
this.initializeBaseStateP.catch((error) => {
|
|
271
|
+
throw new container_utils_1.DataProcessingError(error === null || error === void 0 ? void 0 : error.message, "FailedToInitializeGC", {
|
|
272
|
+
gcEnabled: this.gcEnabled,
|
|
273
|
+
runSweep: this.shouldRunSweep,
|
|
274
|
+
writeAtRoot: this._writeDataAtRoot,
|
|
275
|
+
testMode: this.testMode,
|
|
276
|
+
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
259
280
|
}
|
|
260
|
-
static create(provider, gcOptions, deleteUnusedRoutes, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
|
|
261
|
-
return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata);
|
|
281
|
+
static create(provider, gcOptions, deleteUnusedRoutes, getNodePackagePath, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
|
|
282
|
+
return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, getNodePackagePath, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata);
|
|
262
283
|
}
|
|
263
284
|
/**
|
|
264
285
|
* This tracks two things:
|
|
@@ -293,29 +314,26 @@ class GarbageCollector {
|
|
|
293
314
|
await this.initializeBaseStateP;
|
|
294
315
|
// Let the runtime update its pending state before GC runs.
|
|
295
316
|
await this.provider.updateStateBeforeGC();
|
|
296
|
-
const gcStats = {};
|
|
297
317
|
// Get the runtime's GC data and run GC on the reference graph in it.
|
|
298
318
|
const gcData = await this.provider.getGCData(fullGC);
|
|
299
|
-
this.updateStateSinceLatestRun(gcData);
|
|
300
319
|
const gcResult = garbage_collector_1.runGarbageCollection(gcData.gcNodes, ["/"], logger);
|
|
301
|
-
const
|
|
320
|
+
const gcStats = this.generateStatsAndLogEvents(gcResult);
|
|
321
|
+
// Update the state since the last GC run. There can be nodes that were referenced between the last and
|
|
322
|
+
// the current run. We need to identify than and update their unreferenced state if needed.
|
|
323
|
+
this.updateStateSinceLastRun(gcData);
|
|
302
324
|
// Update the current state of the system based on the GC run.
|
|
325
|
+
const currentTimestampMs = this.getCurrentTimestampMs();
|
|
303
326
|
this.updateCurrentState(gcData, gcResult, currentTimestampMs);
|
|
304
|
-
|
|
327
|
+
this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentTimestampMs);
|
|
305
328
|
if (runSweep) {
|
|
306
329
|
// Placeholder for running sweep logic.
|
|
307
330
|
}
|
|
308
|
-
// Update stats to be reported in the peformance event.
|
|
309
|
-
gcStats.deletedNodes = gcResult.deletedNodeIds.length;
|
|
310
|
-
gcStats.totalNodes = gcResult.referencedNodeIds.length + gcResult.deletedNodeIds.length;
|
|
311
|
-
gcStats.deletedDataStores = dataStoreUsedStateStats.unusedNodeCount;
|
|
312
|
-
gcStats.totalDataStores = dataStoreUsedStateStats.totalNodeCount;
|
|
313
331
|
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
314
332
|
// involving access to deleted data.
|
|
315
333
|
if (this.testMode) {
|
|
316
334
|
this.deleteUnusedRoutes(gcResult.deletedNodeIds);
|
|
317
335
|
}
|
|
318
|
-
event.end(gcStats);
|
|
336
|
+
event.end(Object.assign({}, gcStats));
|
|
319
337
|
return gcStats;
|
|
320
338
|
}, { end: true, cancel: "error" });
|
|
321
339
|
}
|
|
@@ -369,32 +387,41 @@ class GarbageCollector {
|
|
|
369
387
|
await this.updateSummaryGCVersionFromSnapshot(result.snapshot, readAndParseBlob);
|
|
370
388
|
}
|
|
371
389
|
/**
|
|
372
|
-
* Called when a node with the given id is
|
|
390
|
+
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
391
|
+
* @param nodePath - The id of the node that changed.
|
|
392
|
+
* @param reason - Whether the node was loaded or changed.
|
|
393
|
+
* @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
|
|
394
|
+
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
373
395
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const nodeId = id.startsWith("/") ? id : `/${id}`;
|
|
378
|
-
(_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.logIfInactive(this.mc.logger, "inactiveObjectChanged", this.getCurrentTimestampMs(), this.deleteTimeoutMs, nodeId);
|
|
379
|
-
}
|
|
380
|
-
dispose() {
|
|
381
|
-
if (this.sessionExpiryTimer !== undefined) {
|
|
382
|
-
clearTimeout(this.sessionExpiryTimer);
|
|
383
|
-
this.sessionExpiryTimer = undefined;
|
|
396
|
+
nodeUpdated(nodePath, reason, packagePath, requestHeaders) {
|
|
397
|
+
if (!this.shouldRunGC) {
|
|
398
|
+
return;
|
|
384
399
|
}
|
|
400
|
+
this.logIfInactive(reason, nodePath, this.getCurrentTimestampMs(), packagePath, requestHeaders);
|
|
385
401
|
}
|
|
386
402
|
/**
|
|
387
403
|
* Called when an outbound reference is added to a node. This is used to identify all nodes that have been
|
|
388
404
|
* referenced between summaries so that their unreferenced timestamp can be reset.
|
|
389
405
|
*
|
|
390
|
-
* @param
|
|
391
|
-
* @param
|
|
406
|
+
* @param fromNodePath - The node from which the reference is added.
|
|
407
|
+
* @param toNodePath - The node to which the reference is added.
|
|
392
408
|
*/
|
|
393
|
-
addedOutboundReference(
|
|
409
|
+
addedOutboundReference(fromNodePath, toNodePath) {
|
|
394
410
|
var _a;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
411
|
+
if (!this.shouldRunGC) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const outboundRoutes = (_a = this.referencesSinceLastRun.get(fromNodePath)) !== null && _a !== void 0 ? _a : [];
|
|
415
|
+
outboundRoutes.push(toNodePath);
|
|
416
|
+
this.referencesSinceLastRun.set(fromNodePath, outboundRoutes);
|
|
417
|
+
// If the node that got referenced is inactive, log an event as that may indicate use-after-delete.
|
|
418
|
+
this.logIfInactive("Revived", toNodePath, this.getCurrentTimestampMs(), this.getNodePackagePath(toNodePath));
|
|
419
|
+
}
|
|
420
|
+
dispose() {
|
|
421
|
+
if (this.sessionExpiryTimer !== undefined) {
|
|
422
|
+
clearTimeout(this.sessionExpiryTimer);
|
|
423
|
+
this.sessionExpiryTimer = undefined;
|
|
424
|
+
}
|
|
398
425
|
}
|
|
399
426
|
/**
|
|
400
427
|
* Update the latest summary GC version from the metadata blob in the given snapshot.
|
|
@@ -435,9 +462,6 @@ class GarbageCollector {
|
|
|
435
462
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
436
463
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
437
464
|
if (nodeStateTracker !== undefined) {
|
|
438
|
-
// If this node has been unreferenced for longer than deleteTimeoutMs and is being referenced,
|
|
439
|
-
// log an error as this may mean the deleteTimeoutMs is not long enough.
|
|
440
|
-
nodeStateTracker.logIfInactive(this.mc.logger, "inactiveObjectRevived", currentTimestampMs, this.deleteTimeoutMs, nodeId);
|
|
441
465
|
// Stop tracking so as to clear out any running timers.
|
|
442
466
|
nodeStateTracker.stopTracking();
|
|
443
467
|
// Delete the node as we don't need to track it any more.
|
|
@@ -453,7 +477,7 @@ class GarbageCollector {
|
|
|
453
477
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
454
478
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
455
479
|
*/
|
|
456
|
-
|
|
480
|
+
updateStateSinceLastRun(currentGCData) {
|
|
457
481
|
// If we haven't run GC before or no references were added since the last run, there is nothing to do.
|
|
458
482
|
if (this.gcDataFromLastRun === undefined || this.referencesSinceLastRun.size === 0) {
|
|
459
483
|
return;
|
|
@@ -509,8 +533,7 @@ class GarbageCollector {
|
|
|
509
533
|
* @param currentGCData - The GC data (reference graph) from the current GC run.
|
|
510
534
|
*/
|
|
511
535
|
validateReferenceCorrectness(currentGCData) {
|
|
512
|
-
common_utils_1.assert(this.gcDataFromLastRun !== undefined, 0x2b7
|
|
513
|
-
/* "Can't validate correctness without GC data from last run" */ );
|
|
536
|
+
common_utils_1.assert(this.gcDataFromLastRun !== undefined, 0x2b7);
|
|
514
537
|
// Get a list of all the outbound routes (or references) in the current GC data.
|
|
515
538
|
const currentReferences = [];
|
|
516
539
|
for (const [nodeId, outboundRoutes] of Object.entries(currentGCData.gcNodes)) {
|
|
@@ -534,9 +557,9 @@ class GarbageCollector {
|
|
|
534
557
|
// Validate that the current reference graph doesn't have references that we are not already aware of. If this
|
|
535
558
|
// happens, it might indicate data corruption since we may delete objects prematurely.
|
|
536
559
|
currentReferences.forEach((route) => {
|
|
537
|
-
// Validate references for data stores only
|
|
538
|
-
//
|
|
539
|
-
if (route
|
|
560
|
+
// Validate references for data stores only. Currently, layers below data stores don't have GC implemented
|
|
561
|
+
// so there is no guarantee their references will be notified.
|
|
562
|
+
if (isDataStoreNode(route) && !explicitReferences.includes(route)) {
|
|
540
563
|
/**
|
|
541
564
|
* The following log will be enabled once this issue is resolved:
|
|
542
565
|
* https://github.com/microsoft/FluidFramework/issues/8878.
|
|
@@ -550,12 +573,96 @@ class GarbageCollector {
|
|
|
550
573
|
}
|
|
551
574
|
});
|
|
552
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Generates the stats of a garbage collection run from the given results of the run. Also, logs any pending events
|
|
578
|
+
* in the pendingEventsQueue.
|
|
579
|
+
* @param gcResult - The result of a GC run.
|
|
580
|
+
* @returns the GC stats of the GC run.
|
|
581
|
+
*/
|
|
582
|
+
generateStatsAndLogEvents(gcResult) {
|
|
583
|
+
// Log pending events for unreferenced nodes after GC has run. We should have the package data available for
|
|
584
|
+
// them now since the GC run should have loaded these nodes.
|
|
585
|
+
let event = this.pendingEventsQueue.shift();
|
|
586
|
+
while (event !== undefined) {
|
|
587
|
+
const pkg = this.getNodePackagePath(event.id);
|
|
588
|
+
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: pkg ? { value: `/${pkg.join("/")}`, tag: telemetry_utils_1.TelemetryDataTag.PackageData } : undefined }));
|
|
589
|
+
event = this.pendingEventsQueue.shift();
|
|
590
|
+
}
|
|
591
|
+
const gcStats = {
|
|
592
|
+
nodeCount: 0,
|
|
593
|
+
dataStoreCount: 0,
|
|
594
|
+
unrefNodeCount: 0,
|
|
595
|
+
unrefDataStoreCount: 0,
|
|
596
|
+
updatedNodeCount: 0,
|
|
597
|
+
updatedDataStoreCount: 0,
|
|
598
|
+
};
|
|
599
|
+
for (const nodeId of gcResult.referencedNodeIds) {
|
|
600
|
+
gcStats.nodeCount++;
|
|
601
|
+
const isDataStore = isDataStoreNode(nodeId);
|
|
602
|
+
if (isDataStore) {
|
|
603
|
+
gcStats.dataStoreCount++;
|
|
604
|
+
}
|
|
605
|
+
// If a referenced node has an entry in `unreferencedNodesState`, it was previously unreferenced. So, its
|
|
606
|
+
// reference state updated from the last GC run.
|
|
607
|
+
if (this.unreferencedNodesState.has(nodeId)) {
|
|
608
|
+
gcStats.updatedNodeCount++;
|
|
609
|
+
if (isDataStore) {
|
|
610
|
+
gcStats.updatedDataStoreCount++;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
for (const nodeId of gcResult.deletedNodeIds) {
|
|
615
|
+
gcStats.nodeCount++;
|
|
616
|
+
gcStats.unrefNodeCount++;
|
|
617
|
+
const isDataStore = isDataStoreNode(nodeId);
|
|
618
|
+
if (isDataStore) {
|
|
619
|
+
gcStats.dataStoreCount++;
|
|
620
|
+
gcStats.unrefDataStoreCount++;
|
|
621
|
+
}
|
|
622
|
+
// If an unreferenced node doesn't an entry in `unreferencedNodesState`, it was previously referenced. So,
|
|
623
|
+
// its reference state updated from the last GC run.
|
|
624
|
+
if (!this.unreferencedNodesState.has(nodeId)) {
|
|
625
|
+
gcStats.updatedNodeCount++;
|
|
626
|
+
if (isDataStore) {
|
|
627
|
+
gcStats.updatedDataStoreCount++;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return gcStats;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Logs an event if a node is inactive and is used. If the package data for the node exists, log immediately. Else,
|
|
635
|
+
* queue it and it will be logged the next time GC runs as the package data should be available then.
|
|
636
|
+
*/
|
|
637
|
+
logIfInactive(eventSuffix, nodeId, timestampMs, packagePath, requestHeaders) {
|
|
638
|
+
const eventName = `inactiveObject_${eventSuffix}`;
|
|
639
|
+
// We log a particular event for a given node only once so that it is not too noisy.
|
|
640
|
+
const uniqueEventId = `${nodeId}-${eventName}`;
|
|
641
|
+
const nodeState = this.unreferencedNodesState.get(nodeId);
|
|
642
|
+
if ((nodeState === null || nodeState === void 0 ? void 0 : nodeState.inactive) && !this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
643
|
+
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
644
|
+
const event = {
|
|
645
|
+
eventName,
|
|
646
|
+
id: nodeId,
|
|
647
|
+
age: timestampMs - nodeState.unreferencedTimestampMs,
|
|
648
|
+
timeout: this.deleteTimeoutMs,
|
|
649
|
+
externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[_1.RuntimeHeaders.externalRequest],
|
|
650
|
+
viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[_1.RuntimeHeaders.viaHandle],
|
|
651
|
+
};
|
|
652
|
+
if (packagePath !== undefined) {
|
|
653
|
+
this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: { value: `/${packagePath.join("/")}`, tag: telemetry_utils_1.TelemetryDataTag.PackageData } }));
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
this.pendingEventsQueue.push(event);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
553
660
|
}
|
|
554
661
|
exports.GarbageCollector = GarbageCollector;
|
|
555
662
|
/**
|
|
556
663
|
* Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
|
|
557
664
|
* Merge the GC state from all such blobs and return the merged GC state.
|
|
558
|
-
*/
|
|
665
|
+
*/
|
|
559
666
|
async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
560
667
|
let rootGCState = { gcNodes: {} };
|
|
561
668
|
for (const key of Object.keys(gcSnapshotTree.blobs)) {
|
|
@@ -574,4 +681,31 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
|
|
|
574
681
|
}
|
|
575
682
|
return rootGCState;
|
|
576
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* setLongTimeout is used for timeouts longer than setTimeout's ~24.8 day max
|
|
686
|
+
* @param timeoutMs - the total time the timeout needs to last in ms
|
|
687
|
+
* @param timeoutFn - the function to execute when the timer ends
|
|
688
|
+
* @param setTimerFn - the function used to update your timer variable
|
|
689
|
+
*/
|
|
690
|
+
function setLongTimeout(timeoutMs, timeoutFn, setTimerFn) {
|
|
691
|
+
// The setTimeout max is 24.8 days before looping occurs.
|
|
692
|
+
const maxTimeout = 2147483647;
|
|
693
|
+
let timer;
|
|
694
|
+
if (timeoutMs > maxTimeout) {
|
|
695
|
+
const newTimeoutMs = timeoutMs - maxTimeout;
|
|
696
|
+
timer = setTimeout(() => setLongTimeout(newTimeoutMs, timeoutFn, setTimerFn), maxTimeout);
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
timer = setTimeout(() => timeoutFn(), timeoutMs);
|
|
700
|
+
}
|
|
701
|
+
setTimerFn(timer);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Given a GC nodeId, tells whether it belongs to a data store or not.
|
|
705
|
+
*/
|
|
706
|
+
function isDataStoreNode(nodeId) {
|
|
707
|
+
const pathParts = nodeId.split("/");
|
|
708
|
+
// Data store ids are in the format "/dataStoreId".
|
|
709
|
+
return pathParts.length === 2 && pathParts[1] !== "" ? true : false;
|
|
710
|
+
}
|
|
577
711
|
//# sourceMappingURL=garbageCollection.js.map
|