@fluidframework/runtime-utils 0.47.1 → 0.48.0-38105
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/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/summarizerNode/summarizerNodeWithGc.d.ts +9 -4
- package/dist/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summarizerNode/summarizerNodeWithGc.js +74 -23
- package/dist/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/dist/summaryUtils.d.ts.map +1 -1
- package/dist/summaryUtils.js +0 -2
- package/dist/summaryUtils.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/summarizerNode/summarizerNodeWithGc.d.ts +9 -4
- package/lib/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summarizerNode/summarizerNodeWithGc.js +75 -24
- package/lib/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/summaryUtils.d.ts.map +1 -1
- package/lib/summaryUtils.js +0 -2
- package/lib/summaryUtils.js.map +1 -1
- package/package.json +9 -8
- package/src/packageVersion.ts +1 -1
- package/src/summarizerNode/summarizerNodeWithGc.ts +102 -32
- package/src/summaryUtils.ts +0 -3
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import { assert, LazyPromise } from "@fluidframework/common-utils";
|
|
7
|
+
import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
8
8
|
import { cloneGCData } from "@fluidframework/garbage-collector";
|
|
9
|
-
import { ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
9
|
+
import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
10
10
|
import {
|
|
11
11
|
CreateChildSummarizerNodeParam,
|
|
12
12
|
gcBlobKey,
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
SummaryNode,
|
|
28
28
|
} from "./summarizerNodeUtils";
|
|
29
29
|
|
|
30
|
+
const defaultMaxUnreferencedDurationMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
31
|
+
|
|
30
32
|
export interface IRootSummarizerNodeWithGC extends ISummarizerNodeWithGC, ISummarizerNodeRootContract {}
|
|
31
33
|
|
|
32
34
|
// Extend SummaryNode to add used routes tracking to it.
|
|
@@ -62,7 +64,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
62
64
|
private referenceUsedRoutes: string[] | undefined;
|
|
63
65
|
|
|
64
66
|
// The GC details of this node in the initial summary.
|
|
65
|
-
private
|
|
67
|
+
private gcDetailsInInitialSummaryP: LazyPromise<IGarbageCollectionSummaryDetails> | undefined;
|
|
66
68
|
|
|
67
69
|
private gcData: IGarbageCollectionData | undefined;
|
|
68
70
|
|
|
@@ -72,16 +74,16 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
72
74
|
private usedRoutes: string[] = [""];
|
|
73
75
|
|
|
74
76
|
// If this node is marked as unreferenced, the time when it marked as such.
|
|
75
|
-
private
|
|
77
|
+
private unreferencedTimestampMs: number | undefined;
|
|
76
78
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// The max duration for which this node can be unreferenced before it is eligible for deletion.
|
|
80
|
+
private readonly maxUnreferencedDurationMs: number;
|
|
81
|
+
|
|
82
|
+
// The timer that runs when the node is marked unreferenced.
|
|
83
|
+
private readonly unreferencedTimer: Timer;
|
|
84
|
+
|
|
85
|
+
// Tracks whether this node is inactive after being unreferenced for maxUnreferencedDurationMs.
|
|
86
|
+
private inactive: boolean = false;
|
|
85
87
|
|
|
86
88
|
// True if GC is disabled for this node. If so, do not track GC specific state for a summary.
|
|
87
89
|
private readonly gcDisabled: boolean;
|
|
@@ -113,6 +115,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
113
115
|
);
|
|
114
116
|
|
|
115
117
|
this.gcDisabled = config.gcDisabled === true;
|
|
118
|
+
this.maxUnreferencedDurationMs = config.maxUnreferencedDurationMs ?? defaultMaxUnreferencedDurationMs;
|
|
116
119
|
|
|
117
120
|
this.gcDetailsInInitialSummaryP = new LazyPromise(async () => {
|
|
118
121
|
// back-compat: 0.32. getInitialGCSummaryDetailsFn() returns undefined in 0.31. Remove undefined check
|
|
@@ -120,6 +123,19 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
120
123
|
const gcSummaryDetails = await getInitialGCSummaryDetailsFn?.();
|
|
121
124
|
return gcSummaryDetails ?? { usedRoutes: [] };
|
|
122
125
|
});
|
|
126
|
+
|
|
127
|
+
this.unreferencedTimer = new Timer(this.maxUnreferencedDurationMs, () => {
|
|
128
|
+
this.inactive = true;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Returns the GC details that may be added to this node's summary.
|
|
133
|
+
public getGCSummaryDetails(): IGarbageCollectionSummaryDetails {
|
|
134
|
+
return {
|
|
135
|
+
gcData: this.gcData,
|
|
136
|
+
usedRoutes: this.usedRoutes,
|
|
137
|
+
unrefTimestamp: this.unreferencedTimestampMs,
|
|
138
|
+
};
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
/**
|
|
@@ -129,25 +145,20 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
129
145
|
* - gcData: The garbage collection data of this node that is required for running GC.
|
|
130
146
|
*/
|
|
131
147
|
private async loadInitialGCSummaryDetails() {
|
|
132
|
-
|
|
133
|
-
if (this.referenceUsedRoutes !== undefined) {
|
|
148
|
+
if (this.gcDetailsInInitialSummaryP === undefined) {
|
|
134
149
|
return;
|
|
135
150
|
}
|
|
136
151
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
// Possible re-entrancy. It's possible that referenceUsedRoutes was set while we were waiting to get the
|
|
140
|
-
// initial GC details.
|
|
141
|
-
if (this.referenceUsedRoutes !== undefined) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
152
|
+
const gcDetailsInInitialSummaryP = this.gcDetailsInInitialSummaryP;
|
|
153
|
+
this.gcDetailsInInitialSummaryP = undefined;
|
|
144
154
|
|
|
145
|
-
|
|
155
|
+
const gcDetailsInInitialSummary = await gcDetailsInInitialSummaryP;
|
|
146
156
|
// If the GC details has GC data, initialize our GC data from it.
|
|
147
157
|
if (gcDetailsInInitialSummary.gcData !== undefined) {
|
|
148
158
|
this.gcData = cloneGCData(gcDetailsInInitialSummary.gcData);
|
|
149
159
|
}
|
|
150
|
-
this.
|
|
160
|
+
this.referenceUsedRoutes = gcDetailsInInitialSummary.usedRoutes;
|
|
161
|
+
this.unreferencedTimestampMs = gcDetailsInInitialSummary.unrefTimestamp;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
public async summarize(fullTree: boolean, trackState: boolean = true): Promise<ISummarizeResult> {
|
|
@@ -326,6 +337,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
326
337
|
...config,
|
|
327
338
|
// Propagate our gcDisabled state to the child if its not explicity specified in child's config.
|
|
328
339
|
gcDisabled: config.gcDisabled ?? this.gcDisabled,
|
|
340
|
+
maxUnreferencedDurationMs: config.maxUnreferencedDurationMs ?? this.maxUnreferencedDurationMs,
|
|
329
341
|
},
|
|
330
342
|
createDetails.changeSequenceNumber,
|
|
331
343
|
createDetails.latestSummary,
|
|
@@ -369,20 +381,78 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
369
381
|
// are in the same order.
|
|
370
382
|
this.usedRoutes = usedRoutes.sort();
|
|
371
383
|
|
|
372
|
-
// If this node is referenced, clear unreferencedTimestamp, if any.
|
|
373
|
-
// If this node is not referenced and unreferencedTimestamp is undefined, it just became unreferenced. Update
|
|
374
|
-
// unreferencedTimestamp to the gcTimestamp.
|
|
375
|
-
if (this.isReferenced()) {
|
|
376
|
-
this.unreferencedTimestamp = undefined;
|
|
377
|
-
} else if (this.unreferencedTimestamp === undefined) {
|
|
378
|
-
this.unreferencedTimestamp = gcTimestamp;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
384
|
// If GC is not disabled and we are tracking a summary, update the work-in-progress used routes so that it can
|
|
382
385
|
// be tracked for this summary.
|
|
383
386
|
if (!this.gcDisabled && this.isTrackingInProgress()) {
|
|
384
387
|
this.wipSerializedUsedRoutes = JSON.stringify(this.usedRoutes);
|
|
385
388
|
}
|
|
389
|
+
|
|
390
|
+
if (this.isReferenced()) {
|
|
391
|
+
// If this node has been unreferenced for longer than maxUnreferencedDurationMs and is being referenced,
|
|
392
|
+
// log an error as this may mean the maxUnreferencedDurationMs is not long enough.
|
|
393
|
+
this.logErrorIfInactive("inactiveObjectRevived", gcTimestamp);
|
|
394
|
+
|
|
395
|
+
// Clear unreferenced / inactive state, if any.
|
|
396
|
+
this.inactive = false;
|
|
397
|
+
this.unreferencedTimestampMs = undefined;
|
|
398
|
+
this.unreferencedTimer.clear();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// This node is unreferenced. We need to check if this node is inative or if we need to start the unreferenced
|
|
403
|
+
// timer which would mark the node as inactive.
|
|
404
|
+
|
|
405
|
+
// If there is no timestamp when GC was run, we don't have enough information to determine whether this content
|
|
406
|
+
// should become inactive.
|
|
407
|
+
if (gcTimestamp === undefined) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// If unreferencedTimestampMs is not present, this node just became unreferenced. Update unreferencedTimestampMs
|
|
412
|
+
// and start the unreferenced timer.
|
|
413
|
+
// Note that it's possible this node has been unreferenced before but the unreferencedTimestampMs was not added.
|
|
414
|
+
// For example, older versions where this concept did not exist or if gcTimestamp wasn't available. In such
|
|
415
|
+
// cases, we track them as if the content just became unreferenced.
|
|
416
|
+
if (this.unreferencedTimestampMs === undefined) {
|
|
417
|
+
this.unreferencedTimestampMs = gcTimestamp;
|
|
418
|
+
this.unreferencedTimer.start();
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// If we are here, this node was unreferenced earlier.
|
|
423
|
+
|
|
424
|
+
// If it is already inactive or has an unreferenced timer running, there is no more work to be done.
|
|
425
|
+
if (this.inactive || this.unreferencedTimer.hasTimer) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// If it has been unreferenced longer than maxUnreferencedDurationMs, mark it as inactive. Otherwise, start the
|
|
430
|
+
// unreferenced timer for the duration left for it to reach maxUnreferencedDurationMs.
|
|
431
|
+
const currentUnreferencedDurationMs = gcTimestamp - this.unreferencedTimestampMs;
|
|
432
|
+
if (currentUnreferencedDurationMs >= this.maxUnreferencedDurationMs) {
|
|
433
|
+
this.inactive = true;
|
|
434
|
+
} else {
|
|
435
|
+
this.unreferencedTimer.start(this.maxUnreferencedDurationMs - currentUnreferencedDurationMs);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
public recordChange(op: ISequencedDocumentMessage): void {
|
|
440
|
+
// If the node is changed after it is inactive, log an error as this may mean use-after-delete.
|
|
441
|
+
this.logErrorIfInactive("inactiveObjectChanged");
|
|
442
|
+
super.recordChange(op);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private logErrorIfInactive(eventName: string, currentTimestampMs?: number) {
|
|
446
|
+
if (this.inactive) {
|
|
447
|
+
assert(
|
|
448
|
+
this.unreferencedTimestampMs !== undefined,
|
|
449
|
+
"Node should not become inactive without setting unreferencedTimestampMs first");
|
|
450
|
+
this.defaultLogger.sendErrorEvent({
|
|
451
|
+
eventName,
|
|
452
|
+
unreferencedDuratonMs: (currentTimestampMs ?? Date.now()) - this.unreferencedTimestampMs,
|
|
453
|
+
maxUnreferencedDurationMs: this.maxUnreferencedDurationMs,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
386
456
|
}
|
|
387
457
|
|
|
388
458
|
/**
|
package/src/summaryUtils.ts
CHANGED