@fluidframework/container-runtime 2.0.0-internal.7.2.0 → 2.0.0-internal.7.2.2
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/api-report/container-runtime.api.md +0 -1
- package/dist/blobManager.d.ts +3 -4
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +17 -36
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +1 -2
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +17 -9
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +38 -29
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +0 -14
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +0 -43
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +6 -3
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +27 -17
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +4 -1
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +14 -5
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +8 -3
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +11 -4
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +89 -44
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +1 -1
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +3 -1
- package/dist/gc/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +10 -2
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +23 -3
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +0 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +2 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/blobManager.d.ts +3 -4
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +18 -37
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +1 -2
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +17 -9
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +38 -29
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +0 -14
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +1 -44
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +6 -3
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +28 -18
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +5 -2
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +14 -5
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +7 -2
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +11 -4
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +90 -45
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +1 -1
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +1 -1
- package/lib/gc/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +10 -2
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +23 -3
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +0 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +2 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +17 -21
- package/src/blobManager.ts +18 -46
- package/src/containerRuntime.ts +31 -12
- package/src/dataStoreContext.ts +15 -2
- package/src/dataStores.ts +1 -67
- package/src/gc/garbageCollection.ts +39 -25
- package/src/gc/gcConfigs.ts +8 -2
- package/src/gc/gcDefinitions.ts +18 -5
- package/src/gc/gcTelemetry.ts +122 -63
- package/src/gc/index.ts +2 -0
- package/src/opLifecycle/opGroupingManager.ts +37 -2
- package/src/opLifecycle/outbox.ts +4 -2
- package/src/packageVersion.ts +1 -1
package/src/dataStores.ts
CHANGED
|
@@ -50,12 +50,7 @@ import { buildSnapshotTree } from "@fluidframework/driver-utils";
|
|
|
50
50
|
import { assert, Lazy } from "@fluidframework/core-utils";
|
|
51
51
|
import { v4 as uuid } from "uuid";
|
|
52
52
|
import { DataStoreContexts } from "./dataStoreContexts";
|
|
53
|
-
import {
|
|
54
|
-
ContainerRuntime,
|
|
55
|
-
defaultRuntimeHeaderData,
|
|
56
|
-
RuntimeHeaderData,
|
|
57
|
-
TombstoneResponseHeaderKey,
|
|
58
|
-
} from "./containerRuntime";
|
|
53
|
+
import { ContainerRuntime, defaultRuntimeHeaderData, RuntimeHeaderData } from "./containerRuntime";
|
|
59
54
|
import {
|
|
60
55
|
FluidDataStoreContext,
|
|
61
56
|
RemoteFluidDataStoreContext,
|
|
@@ -442,9 +437,6 @@ export class DataStores implements IDisposable {
|
|
|
442
437
|
const request: IRequest = { url: id };
|
|
443
438
|
throw responseToException(create404Response(request), request);
|
|
444
439
|
}
|
|
445
|
-
|
|
446
|
-
this.validateNotTombstoned(context, requestHeaderData);
|
|
447
|
-
|
|
448
440
|
return context;
|
|
449
441
|
}
|
|
450
442
|
|
|
@@ -464,8 +456,6 @@ export class DataStores implements IDisposable {
|
|
|
464
456
|
if (context === undefined) {
|
|
465
457
|
return undefined;
|
|
466
458
|
}
|
|
467
|
-
// Check if the data store is tombstoned. If so, we want to log a telemetry event.
|
|
468
|
-
this.checkIfTombstoned(context, requestHeaderData);
|
|
469
459
|
return context;
|
|
470
460
|
}
|
|
471
461
|
|
|
@@ -516,62 +506,6 @@ export class DataStores implements IDisposable {
|
|
|
516
506
|
}
|
|
517
507
|
}
|
|
518
508
|
|
|
519
|
-
/**
|
|
520
|
-
* Checks if the data store has not been marked as tombstone by GC or not.
|
|
521
|
-
* @param context - the data store context in question
|
|
522
|
-
* @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
|
|
523
|
-
* @returns true if the data store is tombstoned. Otherwise, returns false.
|
|
524
|
-
*/
|
|
525
|
-
private checkIfTombstoned(
|
|
526
|
-
context: FluidDataStoreContext,
|
|
527
|
-
requestHeaderData: RuntimeHeaderData,
|
|
528
|
-
) {
|
|
529
|
-
if (!context.tombstoned) {
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
const logErrorEvent =
|
|
533
|
-
this.runtime.gcThrowOnTombstoneLoad && !requestHeaderData.allowTombstone;
|
|
534
|
-
sendGCUnexpectedUsageEvent(
|
|
535
|
-
this.mc,
|
|
536
|
-
{
|
|
537
|
-
eventName: "GC_Tombstone_DataStore_Requested",
|
|
538
|
-
category: logErrorEvent ? "error" : "generic",
|
|
539
|
-
isSummarizerClient: this.runtime.clientDetails.type === summarizerClientType,
|
|
540
|
-
id: context.id,
|
|
541
|
-
headers: JSON.stringify(requestHeaderData),
|
|
542
|
-
gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
|
|
543
|
-
},
|
|
544
|
-
context.isLoaded ? context.packagePath : undefined,
|
|
545
|
-
);
|
|
546
|
-
return true;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Validates that the data store context requested has not been marked as tombstone by GC.
|
|
551
|
-
* @param context - the data store context in question
|
|
552
|
-
* @param request - the request information to log if the validation detects the data store has been tombstoned
|
|
553
|
-
* @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
|
|
554
|
-
*/
|
|
555
|
-
private validateNotTombstoned(
|
|
556
|
-
context: FluidDataStoreContext,
|
|
557
|
-
requestHeaderData: RuntimeHeaderData,
|
|
558
|
-
) {
|
|
559
|
-
if (this.checkIfTombstoned(context, requestHeaderData)) {
|
|
560
|
-
// The requested data store is removed by gc. Create a 404 gc response exception.
|
|
561
|
-
const request: IRequest = { url: context.id };
|
|
562
|
-
const error = responseToException(
|
|
563
|
-
createResponseError(404, "DataStore was deleted", request, {
|
|
564
|
-
[TombstoneResponseHeaderKey]: true,
|
|
565
|
-
}),
|
|
566
|
-
request,
|
|
567
|
-
);
|
|
568
|
-
// Throw an error if configured via options and via request headers.
|
|
569
|
-
if (this.runtime.gcThrowOnTombstoneLoad && !requestHeaderData.allowTombstone) {
|
|
570
|
-
throw error;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
509
|
public processSignal(fluidDataStoreId: string, message: IInboundSignalMessage, local: boolean) {
|
|
576
510
|
this.validateNotDeleted(fluidDataStoreId);
|
|
577
511
|
const context = this.contexts.get(fluidDataStoreId);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { LazyPromise, Timer } from "@fluidframework/core-utils";
|
|
7
|
-
import { IRequest
|
|
7
|
+
import { IRequest } from "@fluidframework/core-interfaces";
|
|
8
8
|
import {
|
|
9
9
|
gcTreeKey,
|
|
10
10
|
IGarbageCollectionData,
|
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
} from "@fluidframework/telemetry-utils";
|
|
24
24
|
|
|
25
25
|
import {
|
|
26
|
-
AllowInactiveRequestHeaderKey,
|
|
27
26
|
InactiveResponseHeaderKey,
|
|
28
|
-
|
|
27
|
+
RuntimeHeaderData,
|
|
28
|
+
TombstoneResponseHeaderKey,
|
|
29
29
|
} from "../containerRuntime";
|
|
30
30
|
import { ClientSessionExpiredError } from "../error";
|
|
31
31
|
import { IRefreshSummaryResult } from "../summary";
|
|
@@ -862,11 +862,13 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
862
862
|
}
|
|
863
863
|
|
|
864
864
|
/**
|
|
865
|
-
* Called when a node with the given id is updated. If the node is inactive, log an error
|
|
865
|
+
* Called when a node with the given id is updated. If the node is inactive or tombstoned, this will log an error
|
|
866
|
+
* or throw an error if failing on incorrect usage is configured.
|
|
866
867
|
* @param nodePath - The path of the node that changed.
|
|
867
868
|
* @param reason - Whether the node was loaded or changed.
|
|
868
869
|
* @param timestampMs - The timestamp when the node changed.
|
|
869
870
|
* @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
|
|
871
|
+
* @param request - The original request for loads to preserve it in telemetry.
|
|
870
872
|
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
871
873
|
*/
|
|
872
874
|
public nodeUpdated(
|
|
@@ -874,12 +876,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
874
876
|
reason: "Loaded" | "Changed",
|
|
875
877
|
timestampMs?: number,
|
|
876
878
|
packagePath?: readonly string[],
|
|
877
|
-
|
|
879
|
+
request?: IRequest,
|
|
880
|
+
headerData?: RuntimeHeaderData,
|
|
878
881
|
) {
|
|
879
882
|
if (!this.configs.shouldRunGC) {
|
|
880
883
|
return;
|
|
881
884
|
}
|
|
882
885
|
|
|
886
|
+
const isTombstoned = this.tombstones.includes(nodePath);
|
|
887
|
+
|
|
883
888
|
// This will log if appropriate
|
|
884
889
|
this.telemetryTracker.nodeUsed({
|
|
885
890
|
id: nodePath,
|
|
@@ -888,35 +893,44 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
888
893
|
timestampMs ?? this.runtime.getCurrentReferenceTimestampMs(),
|
|
889
894
|
packagePath,
|
|
890
895
|
completedGCRuns: this.completedRuns,
|
|
891
|
-
isTombstoned
|
|
896
|
+
isTombstoned,
|
|
892
897
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
893
|
-
|
|
898
|
+
headers: headerData,
|
|
894
899
|
});
|
|
895
900
|
|
|
901
|
+
const nodeType = this.runtime.getNodeType(nodePath);
|
|
902
|
+
|
|
896
903
|
// Unless this is a Loaded event for a Blob or DataStore, we're done after telemetry tracking
|
|
897
|
-
if (
|
|
898
|
-
reason !== "Loaded" ||
|
|
899
|
-
![GCNodeType.Blob, GCNodeType.DataStore].includes(this.runtime.getNodeType(nodePath))
|
|
900
|
-
) {
|
|
904
|
+
if (reason !== "Loaded" || ![GCNodeType.Blob, GCNodeType.DataStore].includes(nodeType)) {
|
|
901
905
|
return;
|
|
902
906
|
}
|
|
903
907
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (shouldThrowOnInactiveLoad && state === "Inactive") {
|
|
912
|
-
const request: IRequest = { url: nodePath };
|
|
913
|
-
const error = responseToException(
|
|
914
|
-
createResponseError(404, "Object is inactive", request, {
|
|
915
|
-
[InactiveResponseHeaderKey]: true,
|
|
908
|
+
const errorRequest: IRequest = request ?? { url: nodePath };
|
|
909
|
+
// If the object is tombstoned and tombstone enforcement is configured, throw an error.
|
|
910
|
+
if (isTombstoned && this.throwOnTombstoneLoad && headerData?.allowTombstone !== true) {
|
|
911
|
+
// The requested data store is removed by gc. Create a 404 gc response exception.
|
|
912
|
+
throw responseToException(
|
|
913
|
+
createResponseError(404, `${nodeType} was tombstoned`, errorRequest, {
|
|
914
|
+
[TombstoneResponseHeaderKey]: true,
|
|
916
915
|
}),
|
|
917
|
-
|
|
916
|
+
errorRequest,
|
|
918
917
|
);
|
|
919
|
-
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// If the object is inactive and inactive enforcement is configured, throw an error.
|
|
921
|
+
if (this.unreferencedNodesState.get(nodePath)?.state === "Inactive") {
|
|
922
|
+
const shouldThrowOnInactiveLoad =
|
|
923
|
+
!this.isSummarizerClient &&
|
|
924
|
+
this.configs.throwOnInactiveLoad === true &&
|
|
925
|
+
headerData?.allowInactive !== true;
|
|
926
|
+
if (shouldThrowOnInactiveLoad) {
|
|
927
|
+
throw responseToException(
|
|
928
|
+
createResponseError(404, `${nodeType} is inactive`, errorRequest, {
|
|
929
|
+
[InactiveResponseHeaderKey]: true,
|
|
930
|
+
}),
|
|
931
|
+
errorRequest,
|
|
932
|
+
);
|
|
933
|
+
}
|
|
920
934
|
}
|
|
921
935
|
}
|
|
922
936
|
|
package/src/gc/gcConfigs.ts
CHANGED
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
runSessionExpiryKey,
|
|
25
25
|
runSweepKey,
|
|
26
26
|
stableGCVersion,
|
|
27
|
-
|
|
27
|
+
throwOnTombstoneLoadOverrideKey,
|
|
28
28
|
throwOnTombstoneUsageKey,
|
|
29
|
+
gcThrowOnTombstoneLoadOptionName,
|
|
29
30
|
} from "./gcDefinitions";
|
|
30
31
|
import { getGCVersion, shouldAllowGcSweep, shouldAllowGcTombstoneEnforcement } from "./gcHelpers";
|
|
31
32
|
|
|
@@ -168,8 +169,13 @@ export function generateGCConfigs(
|
|
|
168
169
|
createParams.metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
|
|
169
170
|
createParams.gcOptions[gcTombstoneGenerationOptionName] /* current */,
|
|
170
171
|
);
|
|
172
|
+
|
|
173
|
+
const throwOnTombstoneLoadConfig =
|
|
174
|
+
mc.config.getBoolean(throwOnTombstoneLoadOverrideKey) ??
|
|
175
|
+
createParams.gcOptions[gcThrowOnTombstoneLoadOptionName] ??
|
|
176
|
+
false;
|
|
171
177
|
const throwOnTombstoneLoad =
|
|
172
|
-
|
|
178
|
+
throwOnTombstoneLoadConfig &&
|
|
173
179
|
tombstoneEnforcementAllowed &&
|
|
174
180
|
!createParams.isSummarizerClient;
|
|
175
181
|
const throwOnTombstoneUsage =
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
7
|
-
import {
|
|
7
|
+
import { IRequest } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
9
9
|
import {
|
|
10
10
|
IGarbageCollectionData,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
ICreateContainerMetadata,
|
|
20
20
|
IRefreshSummaryResult,
|
|
21
21
|
} from "../summary";
|
|
22
|
+
import { RuntimeHeaderData } from "../containerRuntime";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* @public
|
|
@@ -37,6 +38,13 @@ export const nextGCVersion: GCVersion = 4;
|
|
|
37
38
|
* Otherwise, only enforce GC Tombstone if the passed in value matches the persisted value
|
|
38
39
|
*/
|
|
39
40
|
export const gcTombstoneGenerationOptionName = "gcTombstoneGeneration";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This undocumented GC Option (on ContainerRuntime Options) allows an app to enable throwing an error when tombstone
|
|
44
|
+
* object is loaded (requested).
|
|
45
|
+
*/
|
|
46
|
+
export const gcThrowOnTombstoneLoadOptionName = "gcThrowOnTombstoneLoad";
|
|
47
|
+
|
|
40
48
|
/**
|
|
41
49
|
* This GC Option (on ContainerRuntime Options) allows an app to disable GC Sweep on old documents by incrementing this value.
|
|
42
50
|
*
|
|
@@ -58,8 +66,9 @@ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
|
58
66
|
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
59
67
|
/** Config key to disable the tombstone feature, i.e., tombstone information is not read / written into summary. */
|
|
60
68
|
export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
|
|
61
|
-
/** Config key to
|
|
62
|
-
export const
|
|
69
|
+
/** Config key to override throwing an error when tombstone object is loaded (requested). */
|
|
70
|
+
export const throwOnTombstoneLoadOverrideKey =
|
|
71
|
+
"Fluid.GarbageCollection.ThrowOnTombstoneLoadOverride";
|
|
63
72
|
/** Config key to enable throwing an error when tombstone object is used (e.g. outgoing or incoming ops). */
|
|
64
73
|
export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
|
|
65
74
|
/** Config key to enable GC version upgrade. */
|
|
@@ -251,13 +260,17 @@ export interface IGarbageCollector {
|
|
|
251
260
|
getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
|
|
252
261
|
/** Called when the latest summary of the system has been refreshed. */
|
|
253
262
|
refreshLatestSummary(result: IRefreshSummaryResult): Promise<void>;
|
|
254
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Called when a node with the given path is updated. If the node is inactive or tombstoned, this will log an error
|
|
265
|
+
* or throw an error if failing on incorrect usage is configured.
|
|
266
|
+
*/
|
|
255
267
|
nodeUpdated(
|
|
256
268
|
nodePath: string,
|
|
257
269
|
reason: "Loaded" | "Changed",
|
|
258
270
|
timestampMs?: number,
|
|
259
271
|
packagePath?: readonly string[],
|
|
260
|
-
|
|
272
|
+
request?: IRequest,
|
|
273
|
+
headerData?: RuntimeHeaderData,
|
|
261
274
|
): void;
|
|
262
275
|
/** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
|
|
263
276
|
addedOutboundReference(fromNodePath: string, toNodePath: string): void;
|
package/src/gc/gcTelemetry.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
MonitoringContext,
|
|
12
12
|
tagCodeArtifacts,
|
|
13
13
|
} from "@fluidframework/telemetry-utils";
|
|
14
|
+
import { RuntimeHeaderData } from "../containerRuntime";
|
|
14
15
|
import { ICreateContainerMetadata } from "../summary";
|
|
15
16
|
import {
|
|
16
17
|
disableSweepLogKey,
|
|
@@ -19,8 +20,9 @@ import {
|
|
|
19
20
|
IGarbageCollectorConfigs,
|
|
20
21
|
disableTombstoneKey,
|
|
21
22
|
throwOnTombstoneUsageKey,
|
|
22
|
-
|
|
23
|
+
throwOnTombstoneLoadOverrideKey,
|
|
23
24
|
runSweepKey,
|
|
25
|
+
GCFeatureMatrix,
|
|
24
26
|
} from "./gcDefinitions";
|
|
25
27
|
import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
26
28
|
|
|
@@ -32,7 +34,7 @@ interface ICommonProps {
|
|
|
32
34
|
completedGCRuns: number;
|
|
33
35
|
isTombstoned: boolean;
|
|
34
36
|
lastSummaryTime?: number;
|
|
35
|
-
|
|
37
|
+
headers?: RuntimeHeaderData;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/** The event that is logged when unreferenced node is used after a certain time. */
|
|
@@ -45,6 +47,10 @@ interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps
|
|
|
45
47
|
type: GCNodeType;
|
|
46
48
|
unrefTime: number;
|
|
47
49
|
age: number;
|
|
50
|
+
// Expanding GC feature matrix. Without doing this, the configs cannot be logged in telemetry directly.
|
|
51
|
+
gcConfigs: Omit<IGarbageCollectorConfigs, "persistedGcFeatureMatrix"> & {
|
|
52
|
+
[K in keyof GCFeatureMatrix]: GCFeatureMatrix[K];
|
|
53
|
+
};
|
|
48
54
|
timeout?: number;
|
|
49
55
|
fromId?: {
|
|
50
56
|
value: string;
|
|
@@ -78,10 +84,7 @@ export class GCTelemetryTracker {
|
|
|
78
84
|
|
|
79
85
|
constructor(
|
|
80
86
|
private readonly mc: MonitoringContext,
|
|
81
|
-
private readonly configs:
|
|
82
|
-
IGarbageCollectorConfigs,
|
|
83
|
-
"inactiveTimeoutMs" | "sweepTimeoutMs" | "tombstoneEnforcementAllowed"
|
|
84
|
-
>,
|
|
87
|
+
private readonly configs: IGarbageCollectorConfigs,
|
|
85
88
|
private readonly isSummarizerClient: boolean,
|
|
86
89
|
private readonly createContainerMetadata: ICreateContainerMetadata,
|
|
87
90
|
private readonly getNodeType: (nodeId: string) => GCNodeType,
|
|
@@ -94,12 +97,12 @@ export class GCTelemetryTracker {
|
|
|
94
97
|
) {}
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
|
-
* Returns whether an event should be logged for a node that isn't active anymore.
|
|
100
|
+
* Returns whether an event should be logged for a node that isn't active anymore. This does not apply to
|
|
101
|
+
* tombstoned nodes for which an event is always logged. Some scenarios where we won't log:
|
|
98
102
|
* 1. When a DDS is changed. The corresponding data store's event will be logged instead.
|
|
99
103
|
* 2. An event is logged only once per container instance per event per node.
|
|
100
104
|
*/
|
|
101
105
|
private shouldLogNonActiveEvent(
|
|
102
|
-
nodeId: string,
|
|
103
106
|
nodeType: GCNodeType,
|
|
104
107
|
usageType: NodeUsageType,
|
|
105
108
|
nodeStateTracker: UnreferencedStateTracker,
|
|
@@ -119,6 +122,8 @@ export class GCTelemetryTracker {
|
|
|
119
122
|
return false;
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
// Non-tombstone events are logged once per event per node. A unique id is generated by joining
|
|
126
|
+
// node state (inactive / sweep ready), node's id and usage (loaded / changed / revived).
|
|
122
127
|
if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
|
|
123
128
|
return false;
|
|
124
129
|
}
|
|
@@ -126,24 +131,63 @@ export class GCTelemetryTracker {
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
/**
|
|
129
|
-
* Called when a node is used. If the node is not active, log
|
|
134
|
+
* Called when a node is used. If the node is not active or tombstoned, log telemetry indicating object is used
|
|
135
|
+
* when it should not have been.
|
|
130
136
|
*/
|
|
131
137
|
public nodeUsed(nodeUsageProps: INodeUsageProps) {
|
|
132
138
|
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
133
139
|
// logging as nothing interesting would have happened worth logging.
|
|
134
|
-
|
|
135
|
-
const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
|
|
136
|
-
if (!nodeStateTracker || nodeUsageProps.currentReferenceTimestampMs === undefined) {
|
|
140
|
+
if (nodeUsageProps.currentReferenceTimestampMs === undefined) {
|
|
137
141
|
return;
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
// node's id and usage (loaded / changed / revived).
|
|
142
|
-
const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
|
|
144
|
+
const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
|
|
143
145
|
const nodeType = this.getNodeType(nodeUsageProps.id);
|
|
146
|
+
const {
|
|
147
|
+
usageType,
|
|
148
|
+
currentReferenceTimestampMs,
|
|
149
|
+
packagePath,
|
|
150
|
+
id: untaggedId,
|
|
151
|
+
fromId: untaggedFromId,
|
|
152
|
+
...propsToLog
|
|
153
|
+
} = nodeUsageProps;
|
|
154
|
+
const { persistedGcFeatureMatrix, ...configs } = this.configs;
|
|
155
|
+
const unrefEventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
|
|
156
|
+
type: nodeType,
|
|
157
|
+
unrefTime: nodeStateTracker?.unreferencedTimestampMs ?? -1,
|
|
158
|
+
age:
|
|
159
|
+
nodeStateTracker !== undefined
|
|
160
|
+
? nodeUsageProps.currentReferenceTimestampMs -
|
|
161
|
+
nodeStateTracker.unreferencedTimestampMs
|
|
162
|
+
: -1,
|
|
163
|
+
timeout:
|
|
164
|
+
nodeStateTracker?.state === UnreferencedState.Inactive
|
|
165
|
+
? this.configs.inactiveTimeoutMs
|
|
166
|
+
: this.configs.sweepTimeoutMs,
|
|
167
|
+
...tagCodeArtifacts({ id: untaggedId, fromId: untaggedFromId }),
|
|
168
|
+
...propsToLog,
|
|
169
|
+
...this.createContainerMetadata,
|
|
170
|
+
gcConfigs: { ...configs, ...persistedGcFeatureMatrix },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// If the node that is used is tombstoned, log a tombstone telemetry.
|
|
174
|
+
// Note that this is done before checking if "nodeStateTracker" is undefined below because unreferenced
|
|
175
|
+
// tracking may not have yet been enabled. That happens only after the client transitions to write mode.
|
|
176
|
+
if (nodeUsageProps.isTombstoned) {
|
|
177
|
+
this.logTombstoneUsageTelemetry(nodeUsageProps, unrefEventProps, nodeType, usageType);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// After logging tombstone telemetry, if the node's unreferenced state is not tracked, there is nothing
|
|
181
|
+
// else to log.
|
|
182
|
+
if (nodeStateTracker === undefined) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const state = nodeStateTracker.state;
|
|
187
|
+
const uniqueEventId = `${state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
|
|
188
|
+
|
|
144
189
|
if (
|
|
145
190
|
!this.shouldLogNonActiveEvent(
|
|
146
|
-
nodeUsageProps.id,
|
|
147
191
|
nodeType,
|
|
148
192
|
nodeUsageProps.usageType,
|
|
149
193
|
nodeStateTracker,
|
|
@@ -153,42 +197,9 @@ export class GCTelemetryTracker {
|
|
|
153
197
|
return;
|
|
154
198
|
}
|
|
155
199
|
|
|
156
|
-
// Add the unique event id so that we don't generate a log for this event again in this session
|
|
200
|
+
// Add the unique event id so that we don't generate a log for this event again in this session.
|
|
157
201
|
this.loggedUnreferencedEvents.add(uniqueEventId);
|
|
158
202
|
|
|
159
|
-
const state = nodeStateTracker.state;
|
|
160
|
-
const { usageType, currentReferenceTimestampMs, packagePath, id, fromId, ...propsToLog } =
|
|
161
|
-
nodeUsageProps;
|
|
162
|
-
const eventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
|
|
163
|
-
type: nodeType,
|
|
164
|
-
unrefTime: nodeStateTracker.unreferencedTimestampMs,
|
|
165
|
-
age:
|
|
166
|
-
nodeUsageProps.currentReferenceTimestampMs -
|
|
167
|
-
nodeStateTracker.unreferencedTimestampMs,
|
|
168
|
-
timeout:
|
|
169
|
-
state === UnreferencedState.Inactive
|
|
170
|
-
? this.configs.inactiveTimeoutMs
|
|
171
|
-
: this.configs.sweepTimeoutMs,
|
|
172
|
-
...tagCodeArtifacts({ id, fromId }),
|
|
173
|
-
...propsToLog,
|
|
174
|
-
...this.createContainerMetadata,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// This will log the following events:
|
|
178
|
-
// GC_Tombstone_DataStore_Revived, GC_Tombstone_SubDataStore_Revived, GC_Tombstone_Blob_Revived
|
|
179
|
-
if (nodeUsageProps.usageType === "Revived" && nodeUsageProps.isTombstoned) {
|
|
180
|
-
sendGCUnexpectedUsageEvent(
|
|
181
|
-
this.mc,
|
|
182
|
-
{
|
|
183
|
-
eventName: `GC_Tombstone_${nodeType}_Revived`,
|
|
184
|
-
category: "generic",
|
|
185
|
-
...tagCodeArtifacts({ url: id }),
|
|
186
|
-
gcTombstoneEnforcementAllowed: this.configs.tombstoneEnforcementAllowed,
|
|
187
|
-
},
|
|
188
|
-
undefined /* packagePath */,
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
203
|
// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
|
|
193
204
|
// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
|
|
194
205
|
// but it's a good signal nonetheless and we can consume it with a grain of salt.
|
|
@@ -196,7 +207,7 @@ export class GCTelemetryTracker {
|
|
|
196
207
|
// SweepReady errors are usages of Objects that will be deleted by GC Sweep!
|
|
197
208
|
if (this.isSummarizerClient) {
|
|
198
209
|
this.pendingEventsQueue.push({
|
|
199
|
-
...
|
|
210
|
+
...unrefEventProps,
|
|
200
211
|
usageType: nodeUsageProps.usageType,
|
|
201
212
|
state,
|
|
202
213
|
});
|
|
@@ -206,16 +217,16 @@ export class GCTelemetryTracker {
|
|
|
206
217
|
// Events generated:
|
|
207
218
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
208
219
|
if (nodeUsageProps.usageType === "Loaded") {
|
|
209
|
-
const { id
|
|
220
|
+
const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
|
|
210
221
|
const event = {
|
|
211
222
|
eventName: `${state}Object_${nodeUsageProps.usageType}`,
|
|
212
|
-
|
|
223
|
+
...tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }),
|
|
213
224
|
stack: generateStack(),
|
|
214
|
-
id
|
|
215
|
-
fromId
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
225
|
+
id,
|
|
226
|
+
fromId,
|
|
227
|
+
headers: { ...headers },
|
|
228
|
+
details: detailedProps,
|
|
229
|
+
gcConfigs,
|
|
219
230
|
};
|
|
220
231
|
|
|
221
232
|
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
@@ -229,6 +240,52 @@ export class GCTelemetryTracker {
|
|
|
229
240
|
}
|
|
230
241
|
}
|
|
231
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Logs telemetry when a tombstoned object is changed, revived or loaded.
|
|
245
|
+
*/
|
|
246
|
+
private logTombstoneUsageTelemetry(
|
|
247
|
+
nodeUsageProps: INodeUsageProps,
|
|
248
|
+
unrefEventProps: Omit<IUnreferencedEventProps, "state" | "usageType">,
|
|
249
|
+
nodeType: GCNodeType,
|
|
250
|
+
usageType: NodeUsageType,
|
|
251
|
+
) {
|
|
252
|
+
// This will log the following events:
|
|
253
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_DataStore_Changed, GC_Tombstone_DataStore_Revived
|
|
254
|
+
// GC_Tombstone_SubDataStore_Requested, GC_Tombstone_SubDataStore_Changed, GC_Tombstone_SubDataStore_Revived
|
|
255
|
+
// GC_Tombstone_Blob_Requested, GC_Tombstone_Blob_Changed, GC_Tombstone_Blob_Revived
|
|
256
|
+
const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
|
|
257
|
+
const eventUsageName = usageType === "Loaded" ? "Requested" : usageType;
|
|
258
|
+
const event = {
|
|
259
|
+
eventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,
|
|
260
|
+
pkg: tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }).pkg,
|
|
261
|
+
stack: generateStack(),
|
|
262
|
+
id,
|
|
263
|
+
fromId,
|
|
264
|
+
headers: { ...headers },
|
|
265
|
+
details: detailedProps,
|
|
266
|
+
gcConfigs,
|
|
267
|
+
tombstoneFlags: {
|
|
268
|
+
DisableTombstone: this.mc.config.getBoolean(disableTombstoneKey),
|
|
269
|
+
ThrowOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
270
|
+
ThrowOnTombstoneLoad: this.mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
|
|
271
|
+
},
|
|
272
|
+
sweepFlags: {
|
|
273
|
+
EnableSweepFlag: this.mc.config.getBoolean(runSweepKey),
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (
|
|
278
|
+
(usageType === "Loaded" &&
|
|
279
|
+
this.configs.throwOnTombstoneLoad &&
|
|
280
|
+
!headers?.allowTombstone) ||
|
|
281
|
+
(usageType === "Changed" && this.configs.throwOnTombstoneUsage)
|
|
282
|
+
) {
|
|
283
|
+
this.mc.logger.sendErrorEvent(event);
|
|
284
|
+
} else {
|
|
285
|
+
this.mc.logger.sendTelemetryEvent(event);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
232
289
|
/**
|
|
233
290
|
* Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.
|
|
234
291
|
* The principle is that every new reference or outbound route must be notified to GC via the
|
|
@@ -295,7 +352,9 @@ export class GCTelemetryTracker {
|
|
|
295
352
|
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
296
353
|
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
297
354
|
for (const eventProps of this.pendingEventsQueue) {
|
|
298
|
-
const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
355
|
+
// const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
356
|
+
const { usageType, state, id, fromId, headers, gcConfigs, ...detailedProps } =
|
|
357
|
+
eventProps;
|
|
299
358
|
/**
|
|
300
359
|
* Revived event is logged only if the node is active. If the node is not active, the reference to it was
|
|
301
360
|
* from another unreferenced node and this scenario is not interesting to log.
|
|
@@ -313,11 +372,11 @@ export class GCTelemetryTracker {
|
|
|
313
372
|
: undefined;
|
|
314
373
|
const event = {
|
|
315
374
|
eventName: `${state}Object_${usageType}`,
|
|
316
|
-
details: JSON.stringify({
|
|
317
|
-
...propsToLog,
|
|
318
|
-
}),
|
|
319
375
|
id,
|
|
320
376
|
fromId,
|
|
377
|
+
headers: { ...headers },
|
|
378
|
+
details: detailedProps,
|
|
379
|
+
gcConfigs,
|
|
321
380
|
...tagCodeArtifacts({
|
|
322
381
|
pkg: pkg?.join("/"),
|
|
323
382
|
fromPkg: fromPkg?.join("/"),
|
|
@@ -404,7 +463,7 @@ export function sendGCUnexpectedUsageEvent(
|
|
|
404
463
|
event.tombstoneFlags = JSON.stringify({
|
|
405
464
|
DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
|
|
406
465
|
ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
407
|
-
ThrowOnTombstoneLoad: mc.config.getBoolean(
|
|
466
|
+
ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
|
|
408
467
|
});
|
|
409
468
|
event.sweepFlags = JSON.stringify({
|
|
410
469
|
EnableSweepFlag: mc.config.getBoolean(runSweepKey),
|
package/src/gc/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export {
|
|
|
12
12
|
GCNodeType,
|
|
13
13
|
gcTestModeKey,
|
|
14
14
|
gcTombstoneGenerationOptionName,
|
|
15
|
+
gcThrowOnTombstoneLoadOptionName,
|
|
15
16
|
gcSweepGenerationOptionName,
|
|
16
17
|
GCFeatureMatrix,
|
|
17
18
|
GCVersion,
|
|
@@ -32,6 +33,7 @@ export {
|
|
|
32
33
|
disableAttachmentBlobSweepKey,
|
|
33
34
|
disableDatastoreSweepKey,
|
|
34
35
|
UnreferencedState,
|
|
36
|
+
throwOnTombstoneLoadOverrideKey,
|
|
35
37
|
} from "./gcDefinitions";
|
|
36
38
|
export {
|
|
37
39
|
cloneGCData,
|