@fluidframework/container-runtime 0.59.3001 → 0.59.4000-71128
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/batchTracker.js +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +10 -7
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.js +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.js +3 -3
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +23 -10
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +161 -51
- package/dist/garbageCollection.js.map +1 -1
- package/dist/opTelemetry.js +2 -2
- package/dist/opTelemetry.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/summaryFormat.js +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/lib/batchTracker.js +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +12 -9
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.js +3 -3
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +23 -10
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +139 -48
- package/lib/garbageCollection.js.map +1 -1
- package/lib/opTelemetry.js +2 -2
- package/lib/opTelemetry.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/summaryFormat.js +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +22 -19
- package/src/batchTracker.ts +1 -1
- package/src/containerRuntime.ts +21 -8
- package/src/dataStoreContext.ts +1 -1
- package/src/dataStores.ts +3 -3
- package/src/garbageCollection.ts +191 -47
- package/src/opTelemetry.ts +2 -2
- package/src/packageVersion.ts +1 -1
- package/src/summaryFormat.ts +1 -1
package/src/garbageCollection.ts
CHANGED
|
@@ -16,15 +16,17 @@ import {
|
|
|
16
16
|
runGarbageCollection,
|
|
17
17
|
unpackChildNodesGCDetails,
|
|
18
18
|
} from "@fluidframework/garbage-collector";
|
|
19
|
-
import { ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
19
|
+
import { ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
|
|
20
20
|
import {
|
|
21
21
|
gcBlobKey,
|
|
22
22
|
IGarbageCollectionData,
|
|
23
23
|
IGarbageCollectionState,
|
|
24
24
|
IGarbageCollectionDetailsBase,
|
|
25
|
-
|
|
25
|
+
IGarbageCollectionNodeData,
|
|
26
|
+
ISummarizeResult,
|
|
26
27
|
} from "@fluidframework/runtime-definitions";
|
|
27
28
|
import {
|
|
29
|
+
mergeStats,
|
|
28
30
|
ReadAndParseBlob,
|
|
29
31
|
RefreshSummaryResult,
|
|
30
32
|
SummaryTreeBuilder,
|
|
@@ -37,8 +39,10 @@ import {
|
|
|
37
39
|
TelemetryDataTag,
|
|
38
40
|
} from "@fluidframework/telemetry-utils";
|
|
39
41
|
|
|
42
|
+
import * as semver from "semver";
|
|
40
43
|
import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
|
|
41
44
|
import { getSummaryForDatastores } from "./dataStores";
|
|
45
|
+
import { pkgVersion } from "./packageVersion";
|
|
42
46
|
import {
|
|
43
47
|
getGCVersion,
|
|
44
48
|
GCVersion,
|
|
@@ -66,13 +70,17 @@ const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
|
66
70
|
// Feature gate key to write GC data at the root of the summary tree.
|
|
67
71
|
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
68
72
|
// Feature gate key to expire a session after a set period of time.
|
|
69
|
-
const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
73
|
+
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
70
74
|
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present
|
|
71
|
-
const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
75
|
+
export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
72
76
|
// Feature gate key to log error messages if GC reference validation fails.
|
|
73
|
-
const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
|
|
77
|
+
export const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
|
|
78
|
+
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
79
|
+
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
80
|
+
// Feature gate key to limit which versions can write the gc blob as a handle if the data is the same.
|
|
81
|
+
export const trackGCStateMinimumVersionKey = "Fluid.GarbageCollection.TrackGCState.MinVersion";
|
|
74
82
|
|
|
75
|
-
const
|
|
83
|
+
const defaultInactiveTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
76
84
|
export const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
77
85
|
|
|
78
86
|
/** The statistics of the system state after a garbage collection run. */
|
|
@@ -114,8 +122,10 @@ export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
|
|
|
114
122
|
interface IUnreferencedEvent {
|
|
115
123
|
eventName: string;
|
|
116
124
|
id: string;
|
|
125
|
+
type: GCNodeType;
|
|
117
126
|
age: number;
|
|
118
127
|
timeout: number;
|
|
128
|
+
completedGCRuns: number;
|
|
119
129
|
lastSummaryTime?: number;
|
|
120
130
|
externalRequest?: boolean;
|
|
121
131
|
viaHandle?: boolean;
|
|
@@ -147,12 +157,13 @@ export interface IGarbageCollector {
|
|
|
147
157
|
readonly summaryStateNeedsReset: boolean;
|
|
148
158
|
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
149
159
|
readonly writeDataAtRoot: boolean;
|
|
160
|
+
readonly trackGCState: boolean;
|
|
150
161
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
151
162
|
collectGarbage(
|
|
152
163
|
options: { logger?: ITelemetryLogger; runGC?: boolean; runSweep?: boolean; fullGC?: boolean; },
|
|
153
164
|
): Promise<IGCStats>;
|
|
154
165
|
/** Summarizes the GC data and returns it as a summary tree. */
|
|
155
|
-
summarize():
|
|
166
|
+
summarize(fullTree: boolean, trackState: boolean): ISummarizeResult | undefined;
|
|
156
167
|
/** Returns the garbage collector specific metadata to be written into the summary. */
|
|
157
168
|
getMetadata(): IGCMetadata;
|
|
158
169
|
/** Returns a map of each node id to its base GC details in the base summary. */
|
|
@@ -252,7 +263,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
252
263
|
readAndParseBlob: ReadAndParseBlob,
|
|
253
264
|
baseLogger: ITelemetryLogger,
|
|
254
265
|
existing: boolean,
|
|
255
|
-
metadata
|
|
266
|
+
metadata: IContainerRuntimeMetadata | undefined,
|
|
267
|
+
isSummarizerClient: boolean,
|
|
256
268
|
): IGarbageCollector {
|
|
257
269
|
return new GarbageCollector(
|
|
258
270
|
provider,
|
|
@@ -264,6 +276,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
264
276
|
baseLogger,
|
|
265
277
|
existing,
|
|
266
278
|
metadata,
|
|
279
|
+
isSummarizerClient,
|
|
267
280
|
);
|
|
268
281
|
}
|
|
269
282
|
|
|
@@ -307,6 +320,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
307
320
|
*/
|
|
308
321
|
private readonly shouldRunSweep: boolean;
|
|
309
322
|
|
|
323
|
+
public readonly trackGCState: boolean;
|
|
324
|
+
|
|
310
325
|
private readonly testMode: boolean;
|
|
311
326
|
private readonly mc: MonitoringContext;
|
|
312
327
|
|
|
@@ -337,6 +352,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
337
352
|
|
|
338
353
|
// Keeps track of the GC state from the last run.
|
|
339
354
|
private previousGCDataFromLastRun: IGarbageCollectionData | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* Keeps track of the serialized GC blob from the latest summary successfully submitted to the server.
|
|
357
|
+
*/
|
|
358
|
+
private latestSerializedSummaryState: string | undefined;
|
|
359
|
+
/**
|
|
360
|
+
* Keeps track of the serialized GC blob from the last GC run of the client.
|
|
361
|
+
*/
|
|
362
|
+
private pendingSerializedSummaryState: string | undefined;
|
|
340
363
|
// Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
|
|
341
364
|
// outbound routes from that node.
|
|
342
365
|
private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
|
|
@@ -345,8 +368,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
345
368
|
private readonly initializeBaseStateP: Promise<void>;
|
|
346
369
|
// The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
|
|
347
370
|
private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
|
|
348
|
-
// The time after which an unreferenced node
|
|
349
|
-
private readonly
|
|
371
|
+
// The time after which an unreferenced node is inactive.
|
|
372
|
+
private readonly inactiveTimeoutMs: number;
|
|
350
373
|
// Map of node ids to their unreferenced state tracker.
|
|
351
374
|
private readonly unreferencedNodesState: Map<string, UnreferencedStateTracker> = new Map();
|
|
352
375
|
// The timeout responsible for closing the container when the session has expired
|
|
@@ -358,6 +381,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
358
381
|
// Queue for unreferenced events that should be logged the next time GC runs.
|
|
359
382
|
private readonly pendingEventsQueue: IUnreferencedEvent[] = [];
|
|
360
383
|
|
|
384
|
+
// The number of times GC has successfully completed on this instance of GarbageCollector.
|
|
385
|
+
private completedRuns = 0;
|
|
386
|
+
|
|
361
387
|
protected constructor(
|
|
362
388
|
private readonly runtime: IGarbageCollectionRuntime,
|
|
363
389
|
private readonly gcOptions: IGCRuntimeOptions,
|
|
@@ -369,12 +395,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
369
395
|
readAndParseBlob: ReadAndParseBlob,
|
|
370
396
|
baseLogger: ITelemetryLogger,
|
|
371
397
|
existing: boolean,
|
|
372
|
-
metadata
|
|
398
|
+
metadata: IContainerRuntimeMetadata | undefined,
|
|
399
|
+
private readonly isSummarizerClient: boolean = true,
|
|
373
400
|
) {
|
|
374
401
|
this.mc = loggerToMonitoringContext(
|
|
375
|
-
ChildLogger.create(baseLogger, "GarbageCollector"))
|
|
376
|
-
|
|
377
|
-
this.deleteTimeoutMs = this.gcOptions.deleteTimeoutMs ?? defaultDeleteTimeoutMs;
|
|
402
|
+
ChildLogger.create(baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }),
|
|
403
|
+
);
|
|
378
404
|
|
|
379
405
|
let prevSummaryGCVersion: number | undefined;
|
|
380
406
|
|
|
@@ -446,6 +472,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
446
472
|
&& !gcOptions.disableGC
|
|
447
473
|
);
|
|
448
474
|
|
|
475
|
+
const minimumVersion = this.mc.config.getString(trackGCStateMinimumVersionKey);
|
|
476
|
+
const shouldTrackStateForVersion = meetsMinimumVersionRequirement(pkgVersion, minimumVersion);
|
|
477
|
+
|
|
478
|
+
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true && shouldTrackStateForVersion;
|
|
479
|
+
|
|
449
480
|
/**
|
|
450
481
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
451
482
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
@@ -456,6 +487,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
456
487
|
this.mc.config.getBoolean(runSweepKey) ?? (this.sessionExpiryTimeoutMs !== undefined && this.sweepEnabled)
|
|
457
488
|
);
|
|
458
489
|
|
|
490
|
+
// Override inactive timeout if test config or gc options to override it is set.
|
|
491
|
+
this.inactiveTimeoutMs =
|
|
492
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs") ??
|
|
493
|
+
this.gcOptions.inactiveTimeoutMs ??
|
|
494
|
+
defaultInactiveTimeoutMs;
|
|
495
|
+
|
|
459
496
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
460
497
|
this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? gcOptions.runGCInTestMode === true;
|
|
461
498
|
|
|
@@ -485,7 +522,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
485
522
|
if (gcSnapshotTree !== undefined) {
|
|
486
523
|
// If the GC tree is written at root, we should also do the same.
|
|
487
524
|
this._writeDataAtRoot = true;
|
|
488
|
-
|
|
525
|
+
const baseGCState = await getGCStateFromSnapshot(
|
|
526
|
+
gcSnapshotTree,
|
|
527
|
+
readAndParseBlob,
|
|
528
|
+
);
|
|
529
|
+
if (this.trackGCState) {
|
|
530
|
+
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
|
|
531
|
+
}
|
|
532
|
+
return baseGCState;
|
|
489
533
|
}
|
|
490
534
|
|
|
491
535
|
// back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
|
|
@@ -524,7 +568,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
524
568
|
gcState.gcNodes[rootId] = { outboundRoutes: Array.from(outboundRoutes) };
|
|
525
569
|
}
|
|
526
570
|
assert(gcState.gcNodes[dsRootId] !== undefined,
|
|
527
|
-
0x2a9 /*
|
|
571
|
+
0x2a9 /* GC nodes for data store not in GC blob */);
|
|
528
572
|
gcState.gcNodes[dsRootId].unreferencedTimestampMs = gcSummaryDetails.unrefTimestamp;
|
|
529
573
|
}
|
|
530
574
|
|
|
@@ -552,7 +596,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
552
596
|
nodeId,
|
|
553
597
|
new UnreferencedStateTracker(
|
|
554
598
|
nodeData.unreferencedTimestampMs,
|
|
555
|
-
this.
|
|
599
|
+
this.inactiveTimeoutMs,
|
|
556
600
|
currentReferenceTimestampMs,
|
|
557
601
|
),
|
|
558
602
|
);
|
|
@@ -597,21 +641,35 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
597
641
|
return baseGCDetailsMap;
|
|
598
642
|
});
|
|
599
643
|
|
|
600
|
-
//
|
|
601
|
-
//
|
|
644
|
+
// Log all the GC options and the state determined by the garbage collector. This is interesting only for the
|
|
645
|
+
// summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
|
|
646
|
+
const gcConfigProps = JSON.stringify({
|
|
647
|
+
gcEnabled: this.gcEnabled,
|
|
648
|
+
sweepEnabled: this.sweepEnabled,
|
|
649
|
+
runGC: this.shouldRunGC,
|
|
650
|
+
runSweep: this.shouldRunSweep,
|
|
651
|
+
writeAtRoot: this._writeDataAtRoot,
|
|
652
|
+
testMode: this.testMode,
|
|
653
|
+
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
654
|
+
inactiveTimeout: this.inactiveTimeoutMs,
|
|
655
|
+
existing,
|
|
656
|
+
...this.gcOptions,
|
|
657
|
+
});
|
|
658
|
+
if (this.isSummarizerClient) {
|
|
659
|
+
this.mc.logger.sendTelemetryEvent({
|
|
660
|
+
eventName: "GarbageCollectorLoaded",
|
|
661
|
+
gcConfigs: gcConfigProps,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Initialize the base state that is used to detect when inactive objects are used.
|
|
602
666
|
if (this.shouldRunGC) {
|
|
603
667
|
this.initializeBaseStateP.catch((error) => {
|
|
604
668
|
const dpe = DataProcessingError.wrapIfUnrecognized(
|
|
605
669
|
error,
|
|
606
670
|
"FailedToInitializeGC",
|
|
607
671
|
);
|
|
608
|
-
dpe.addTelemetryProperties({
|
|
609
|
-
gcEnabled: this.gcEnabled,
|
|
610
|
-
runSweep: this.shouldRunSweep,
|
|
611
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
612
|
-
testMode: this.testMode,
|
|
613
|
-
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
614
|
-
});
|
|
672
|
+
dpe.addTelemetryProperties({ gcConfigs: gcConfigProps });
|
|
615
673
|
throw dpe;
|
|
616
674
|
});
|
|
617
675
|
}
|
|
@@ -632,11 +690,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
632
690
|
},
|
|
633
691
|
): Promise<IGCStats> {
|
|
634
692
|
const {
|
|
635
|
-
logger = this.mc.logger,
|
|
636
693
|
runSweep = this.shouldRunSweep,
|
|
637
694
|
fullGC = this.gcOptions.runFullGC === true || this.summaryStateNeedsReset,
|
|
638
695
|
} = options;
|
|
639
696
|
|
|
697
|
+
const logger = options.logger
|
|
698
|
+
? ChildLogger.create(options.logger, undefined, { all: { completedGCRuns: () => this.completedRuns } })
|
|
699
|
+
: this.mc.logger;
|
|
700
|
+
|
|
640
701
|
return PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
|
|
641
702
|
await this.initializeBaseStateP;
|
|
642
703
|
|
|
@@ -671,7 +732,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
671
732
|
if (this.testMode) {
|
|
672
733
|
this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
|
|
673
734
|
}
|
|
735
|
+
|
|
674
736
|
event.end({ ...gcStats });
|
|
737
|
+
|
|
738
|
+
this.completedRuns++;
|
|
739
|
+
|
|
675
740
|
return gcStats;
|
|
676
741
|
},
|
|
677
742
|
{ end: true, cancel: "error" });
|
|
@@ -682,7 +747,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
682
747
|
* We current write the entire GC state in a single blob. This can be modified later to write multiple
|
|
683
748
|
* blobs. All the blob keys should start with `gcBlobPrefix`.
|
|
684
749
|
*/
|
|
685
|
-
public summarize(
|
|
750
|
+
public summarize(
|
|
751
|
+
fullTree: boolean,
|
|
752
|
+
trackState: boolean,
|
|
753
|
+
): ISummarizeResult | undefined {
|
|
686
754
|
if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
|
|
687
755
|
return;
|
|
688
756
|
}
|
|
@@ -695,8 +763,35 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
695
763
|
};
|
|
696
764
|
}
|
|
697
765
|
|
|
766
|
+
const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
|
|
770
|
+
* instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
|
|
771
|
+
*/
|
|
772
|
+
if (this.trackGCState) {
|
|
773
|
+
this.pendingSerializedSummaryState = newSerializedSummaryState;
|
|
774
|
+
if (
|
|
775
|
+
this.latestSerializedSummaryState !== undefined &&
|
|
776
|
+
this.latestSerializedSummaryState === newSerializedSummaryState &&
|
|
777
|
+
!fullTree &&
|
|
778
|
+
trackState
|
|
779
|
+
) {
|
|
780
|
+
const stats = mergeStats();
|
|
781
|
+
stats.handleNodeCount++;
|
|
782
|
+
return {
|
|
783
|
+
summary: {
|
|
784
|
+
type: SummaryType.Handle,
|
|
785
|
+
handle: `/${gcTreeKey}`,
|
|
786
|
+
handleType: SummaryType.Tree,
|
|
787
|
+
},
|
|
788
|
+
stats,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
698
793
|
const builder = new SummaryTreeBuilder();
|
|
699
|
-
builder.addBlob(`${gcBlobPrefix}_root`,
|
|
794
|
+
builder.addBlob(`${gcBlobPrefix}_root`, newSerializedSummaryState);
|
|
700
795
|
return builder.getSummaryTree();
|
|
701
796
|
}
|
|
702
797
|
|
|
@@ -740,11 +835,32 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
740
835
|
// Basically, it was written in the current GC version.
|
|
741
836
|
if (result.wasSummaryTracked) {
|
|
742
837
|
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
838
|
+
if (this.trackGCState) {
|
|
839
|
+
this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
|
|
840
|
+
this.pendingSerializedSummaryState = undefined;
|
|
841
|
+
}
|
|
743
842
|
return;
|
|
744
843
|
}
|
|
745
|
-
// If the summary was not tracked by this client, update latest GC version from the snapshot in the
|
|
746
|
-
// that is now the latest summary.
|
|
747
|
-
|
|
844
|
+
// If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
|
|
845
|
+
// result as that is now the latest summary.
|
|
846
|
+
const snapshot = result.snapshot;
|
|
847
|
+
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
848
|
+
if (metadataBlobId) {
|
|
849
|
+
const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);
|
|
850
|
+
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const gcSnapshotTree = snapshot.trees[gcTreeKey];
|
|
854
|
+
if (gcSnapshotTree !== undefined && this.trackGCState) {
|
|
855
|
+
const latestGCState = await getGCStateFromSnapshot(
|
|
856
|
+
gcSnapshotTree,
|
|
857
|
+
readAndParseBlob,
|
|
858
|
+
);
|
|
859
|
+
this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
|
|
860
|
+
} else {
|
|
861
|
+
this.latestSerializedSummaryState = undefined;
|
|
862
|
+
}
|
|
863
|
+
this.pendingSerializedSummaryState = undefined;
|
|
748
864
|
}
|
|
749
865
|
|
|
750
866
|
/**
|
|
@@ -805,17 +921,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
805
921
|
}
|
|
806
922
|
}
|
|
807
923
|
|
|
808
|
-
/**
|
|
809
|
-
* Update the latest summary GC version from the metadata blob in the given snapshot.
|
|
810
|
-
*/
|
|
811
|
-
private async updateSummaryGCVersionFromSnapshot(snapshot: ISnapshotTree, readAndParseBlob: ReadAndParseBlob) {
|
|
812
|
-
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
813
|
-
if (metadataBlobId) {
|
|
814
|
-
const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);
|
|
815
|
-
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
924
|
/**
|
|
820
925
|
* Updates the state of the system as per the current GC run. It does the following:
|
|
821
926
|
* 1. Sets up the current GC state as per the gcData.
|
|
@@ -865,7 +970,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
865
970
|
nodeId,
|
|
866
971
|
new UnreferencedStateTracker(
|
|
867
972
|
currentReferenceTimestampMs,
|
|
868
|
-
this.
|
|
973
|
+
this.inactiveTimeoutMs,
|
|
869
974
|
currentReferenceTimestampMs,
|
|
870
975
|
),
|
|
871
976
|
);
|
|
@@ -1090,7 +1195,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1090
1195
|
* Logs an event if a node is inactive and is used.
|
|
1091
1196
|
*/
|
|
1092
1197
|
private logIfInactive(
|
|
1093
|
-
|
|
1198
|
+
eventType: "Changed" | "Loaded" | "Revived",
|
|
1094
1199
|
nodeId: string,
|
|
1095
1200
|
currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs(),
|
|
1096
1201
|
packagePath?: readonly string[],
|
|
@@ -1102,17 +1207,33 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1102
1207
|
return;
|
|
1103
1208
|
}
|
|
1104
1209
|
|
|
1105
|
-
|
|
1210
|
+
// We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
|
|
1211
|
+
// as unreferenced. Also, if an inactive DDS is used, the corresponding data store store will also be used.
|
|
1212
|
+
const nodeType = this.runtime.getNodeType(nodeId);
|
|
1213
|
+
if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
|
|
1218
|
+
// summarizer clients if they are based off of user actions (such as scrolling to content for these objects).
|
|
1219
|
+
if (!this.isSummarizerClient && eventType !== "Loaded") {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const eventName = `inactiveObject_${eventType}`;
|
|
1106
1224
|
// We log a particular event for a given node only once so that it is not too noisy.
|
|
1107
1225
|
const uniqueEventId = `${nodeId}-${eventName}`;
|
|
1108
1226
|
const nodeState = this.unreferencedNodesState.get(nodeId);
|
|
1109
1227
|
if (nodeState?.inactive && !this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
1110
1228
|
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
1229
|
+
// Save all the properties at this point in time so that if we log this later, these values are preserved.
|
|
1111
1230
|
const event: IUnreferencedEvent = {
|
|
1112
1231
|
eventName,
|
|
1113
1232
|
id: nodeId,
|
|
1233
|
+
type: nodeType,
|
|
1114
1234
|
age: currentReferenceTimestampMs - nodeState.unreferencedTimestampMs,
|
|
1115
|
-
timeout: this.
|
|
1235
|
+
timeout: this.inactiveTimeoutMs,
|
|
1236
|
+
completedGCRuns: this.completedRuns,
|
|
1116
1237
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
1117
1238
|
externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
|
|
1118
1239
|
viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
|
|
@@ -1160,6 +1281,17 @@ async function getGCStateFromSnapshot(
|
|
|
1160
1281
|
return rootGCState;
|
|
1161
1282
|
}
|
|
1162
1283
|
|
|
1284
|
+
function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
|
|
1285
|
+
const sortableArray: [string, IGarbageCollectionNodeData][] = Object.entries(gcState.gcNodes);
|
|
1286
|
+
sortableArray.sort(([a], [b]) => a.localeCompare(b));
|
|
1287
|
+
const sortedGCState: IGarbageCollectionState = { gcNodes: {} };
|
|
1288
|
+
for (const [nodeId, nodeData] of sortableArray) {
|
|
1289
|
+
nodeData.outboundRoutes.sort();
|
|
1290
|
+
sortedGCState.gcNodes[nodeId] = nodeData;
|
|
1291
|
+
}
|
|
1292
|
+
return sortedGCState;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1163
1295
|
/**
|
|
1164
1296
|
* setLongTimeout is used for timeouts longer than setTimeout's ~24.8 day max
|
|
1165
1297
|
* @param timeoutMs - the total time the timeout needs to last in ms
|
|
@@ -1182,3 +1314,15 @@ function setLongTimeout(
|
|
|
1182
1314
|
}
|
|
1183
1315
|
setTimerFn(timer);
|
|
1184
1316
|
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* meetsMinimumVersionRequirement is used determining if a feature version should be run. This is similar to feature
|
|
1320
|
+
* flags. The advantage of this is that if we ship a bug in version 0.1.1 and fix it in version 0.2.1. We can keep this
|
|
1321
|
+
* feature disabled for version 0.1.1 and enabled for 0.2.1. Older versions will run without the feature and new
|
|
1322
|
+
* versions will run with the feature.
|
|
1323
|
+
* @param currentVersion - the total time the timeout needs to last in ms
|
|
1324
|
+
* @param minimumVersion - the function to execute when the timer ends
|
|
1325
|
+
*/
|
|
1326
|
+
function meetsMinimumVersionRequirement(currentVersion: string, minimumVersion: string | undefined) {
|
|
1327
|
+
return minimumVersion === undefined || semver.compare(currentVersion, minimumVersion) >= 0;
|
|
1328
|
+
}
|
package/src/opTelemetry.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
ISequencedDocumentMessage,
|
|
10
10
|
ISequencedDocumentSystemMessage,
|
|
11
11
|
} from "@fluidframework/protocol-definitions";
|
|
12
|
-
import {
|
|
12
|
+
import { isRuntimeMessage } from "@fluidframework/driver-utils";
|
|
13
13
|
|
|
14
14
|
export class OpTracker {
|
|
15
15
|
/**
|
|
@@ -49,7 +49,7 @@ export class OpTracker {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
deltaManager.on("op", (message: ISequencedDocumentMessage) => {
|
|
52
|
-
this._nonSystemOpCount +=
|
|
52
|
+
this._nonSystemOpCount += !isRuntimeMessage(message) ? 0 : 1;
|
|
53
53
|
const id = OpTracker.messageId(message);
|
|
54
54
|
this._opsSizeAccumulator += this.messageSize[id] ?? 0;
|
|
55
55
|
this.messageSize.delete(id);
|
package/src/packageVersion.ts
CHANGED
package/src/summaryFormat.ts
CHANGED
|
@@ -231,6 +231,6 @@ export async function getFluidDataStoreAttributes(
|
|
|
231
231
|
// snapshotFormatVersion is at least "0.1" (1), so we don't expect it to be anything else.
|
|
232
232
|
const formatVersion = getAttributesFormatVersion(attributes);
|
|
233
233
|
assert(formatVersion > 0,
|
|
234
|
-
0x1d5 /*
|
|
234
|
+
0x1d5 /* Invalid snapshot format version */);
|
|
235
235
|
return attributes;
|
|
236
236
|
}
|