@fluidframework/container-runtime 2.0.0-dev.7.2.0.204906 → 2.0.0-dev.7.3.0.206769
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/CHANGELOG.md +4 -0
- package/api-report/container-runtime.api.md +4 -7
- package/dist/blobManager.d.ts +5 -5
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +36 -43
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +12 -8
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +24 -12
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +8 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +46 -31
- 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/deltaManagerProxyBase.d.ts +1 -1
- package/dist/deltaManagerProxyBase.d.ts.map +1 -1
- package/dist/deltaManagerProxyBase.js +2 -2
- package/dist/deltaManagerProxyBase.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/gcDefinitions.d.ts +7 -3
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- 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 +72 -39
- package/dist/gc/gcTelemetry.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 +5 -5
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +37 -44
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +12 -8
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +24 -12
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +8 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +46 -31
- 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/deltaManagerProxyBase.d.ts +1 -1
- package/lib/deltaManagerProxyBase.d.ts.map +1 -1
- package/lib/deltaManagerProxyBase.js +2 -2
- package/lib/deltaManagerProxyBase.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/gcDefinitions.d.ts +7 -3
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- 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 +72 -39
- package/lib/gc/gcTelemetry.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 +19 -19
- package/src/blobManager.ts +43 -53
- package/src/containerRuntime.ts +41 -19
- package/src/dataStoreContext.ts +23 -4
- package/src/dataStores.ts +1 -67
- package/src/deltaManagerProxyBase.ts +2 -2
- package/src/gc/garbageCollection.ts +39 -25
- package/src/gc/gcDefinitions.ts +8 -3
- package/src/gc/gcTelemetry.ts +102 -54
- package/src/packageVersion.ts +1 -1
- package/dist/container-runtime-alpha.d.ts +0 -1744
- package/dist/container-runtime-beta.d.ts +0 -1744
- package/dist/container-runtime-public.d.ts +0 -1744
- package/dist/container-runtime-untrimmed.d.ts +0 -1805
package/src/containerRuntime.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ILoaderOptions,
|
|
26
26
|
ILoader,
|
|
27
27
|
LoaderHeader,
|
|
28
|
+
IGetPendingLocalStateProps,
|
|
28
29
|
} from "@fluidframework/container-definitions";
|
|
29
30
|
import {
|
|
30
31
|
IContainerRuntime,
|
|
@@ -512,6 +513,7 @@ export interface RuntimeHeaderData {
|
|
|
512
513
|
wait?: boolean;
|
|
513
514
|
viaHandle?: boolean;
|
|
514
515
|
allowTombstone?: boolean;
|
|
516
|
+
allowInactive?: boolean;
|
|
515
517
|
}
|
|
516
518
|
|
|
517
519
|
/** Default values for Runtime Headers */
|
|
@@ -519,6 +521,7 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
|
|
|
519
521
|
wait: true,
|
|
520
522
|
viaHandle: false,
|
|
521
523
|
allowTombstone: false,
|
|
524
|
+
allowInactive: false,
|
|
522
525
|
};
|
|
523
526
|
|
|
524
527
|
/**
|
|
@@ -988,7 +991,7 @@ export class ContainerRuntime
|
|
|
988
991
|
summaryOp: ISummaryContent,
|
|
989
992
|
referenceSequenceNumber?: number,
|
|
990
993
|
) => number;
|
|
991
|
-
private readonly submitSignalFn: (
|
|
994
|
+
private readonly submitSignalFn: (content: any, targetClientId?: string) => void;
|
|
992
995
|
public readonly disposeFn: (error?: ICriticalContainerError) => void;
|
|
993
996
|
public readonly closeFn: (error?: ICriticalContainerError) => void;
|
|
994
997
|
|
|
@@ -1185,11 +1188,6 @@ export class ContainerRuntime
|
|
|
1185
1188
|
return this.garbageCollector.tombstoneEnforcementAllowed;
|
|
1186
1189
|
}
|
|
1187
1190
|
|
|
1188
|
-
/** If true, throw an error when a tombstone data store is retrieved */
|
|
1189
|
-
public get gcThrowOnTombstoneLoad(): boolean {
|
|
1190
|
-
return this.garbageCollector.throwOnTombstoneLoad;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
1191
|
/** If true, throw an error when a tombstone data store is used. */
|
|
1194
1192
|
public get gcThrowOnTombstoneUsage(): boolean {
|
|
1195
1193
|
return this.garbageCollector.throwOnTombstoneUsage;
|
|
@@ -1871,6 +1869,9 @@ export class ContainerRuntime
|
|
|
1871
1869
|
if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
|
|
1872
1870
|
headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
|
|
1873
1871
|
}
|
|
1872
|
+
if (typeof request.headers?.[AllowInactiveRequestHeaderKey] === "boolean") {
|
|
1873
|
+
headerData.allowInactive = request.headers[AllowInactiveRequestHeaderKey];
|
|
1874
|
+
}
|
|
1874
1875
|
|
|
1875
1876
|
// We allow Tombstone requests for sub-DataStore objects
|
|
1876
1877
|
if (requestForChild) {
|
|
@@ -1880,20 +1881,24 @@ export class ContainerRuntime
|
|
|
1880
1881
|
await this.dataStores.waitIfPendingAlias(id);
|
|
1881
1882
|
const internalId = this.internalId(id);
|
|
1882
1883
|
const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
|
|
1883
|
-
const dataStoreChannel = await dataStoreContext.realize();
|
|
1884
1884
|
|
|
1885
1885
|
// Remove query params, leading and trailing slashes from the url. This is done to make sure the format is
|
|
1886
1886
|
// the same as GC nodes id.
|
|
1887
1887
|
const urlWithoutQuery = trimLeadingAndTrailingSlashes(request.url.split("?")[0]);
|
|
1888
|
+
// Get the initial snapshot details which contain the data store package path.
|
|
1889
|
+
const details = await dataStoreContext.getInitialSnapshotDetails();
|
|
1890
|
+
|
|
1891
|
+
// Note that this will throw if the data store is inactive or tombstoned and throwing on incorrect usage
|
|
1892
|
+
// is configured.
|
|
1888
1893
|
this.garbageCollector.nodeUpdated(
|
|
1889
1894
|
`/${urlWithoutQuery}`,
|
|
1890
1895
|
"Loaded",
|
|
1891
1896
|
undefined /* timestampMs */,
|
|
1892
|
-
|
|
1893
|
-
request
|
|
1897
|
+
details.pkg,
|
|
1898
|
+
request,
|
|
1899
|
+
headerData,
|
|
1894
1900
|
);
|
|
1895
|
-
|
|
1896
|
-
return dataStoreChannel;
|
|
1901
|
+
return dataStoreContext.realize();
|
|
1897
1902
|
}
|
|
1898
1903
|
|
|
1899
1904
|
/** Adds the container's metadata to the given summary tree. */
|
|
@@ -2537,6 +2542,12 @@ export class ContainerRuntime
|
|
|
2537
2542
|
"entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
|
|
2538
2543
|
);
|
|
2539
2544
|
}
|
|
2545
|
+
this.garbageCollector.nodeUpdated(
|
|
2546
|
+
`/${internalId}`,
|
|
2547
|
+
"Loaded",
|
|
2548
|
+
undefined /* timestampMs */,
|
|
2549
|
+
context.packagePath,
|
|
2550
|
+
);
|
|
2540
2551
|
return channel.entryPoint;
|
|
2541
2552
|
}
|
|
2542
2553
|
|
|
@@ -2663,16 +2674,28 @@ export class ContainerRuntime
|
|
|
2663
2674
|
* Submits the signal to be sent to other clients.
|
|
2664
2675
|
* @param type - Type of the signal.
|
|
2665
2676
|
* @param content - Content of the signal.
|
|
2677
|
+
* @param targetClientId - When specified, the signal is only sent to the provided client id.
|
|
2666
2678
|
*/
|
|
2667
|
-
public submitSignal(type: string, content: any) {
|
|
2679
|
+
public submitSignal(type: string, content: any, targetClientId?: string) {
|
|
2668
2680
|
this.verifyNotClosed();
|
|
2669
2681
|
const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
|
|
2670
|
-
return this.submitSignalFn(envelope);
|
|
2682
|
+
return this.submitSignalFn(envelope, targetClientId);
|
|
2671
2683
|
}
|
|
2672
2684
|
|
|
2673
|
-
|
|
2685
|
+
/**
|
|
2686
|
+
* Submits the signal to be sent to other clients.
|
|
2687
|
+
* @param type - Type of the signal.
|
|
2688
|
+
* @param content - Content of the signal.
|
|
2689
|
+
* @param targetClientId - When specified, the signal is only sent to the provided client id.
|
|
2690
|
+
*/
|
|
2691
|
+
public submitDataStoreSignal(
|
|
2692
|
+
address: string,
|
|
2693
|
+
type: string,
|
|
2694
|
+
content: any,
|
|
2695
|
+
targetClientId?: string,
|
|
2696
|
+
) {
|
|
2674
2697
|
const envelope = this.createNewSignalEnvelope(address, type, content);
|
|
2675
|
-
return this.submitSignalFn(envelope);
|
|
2698
|
+
return this.submitSignalFn(envelope, targetClientId);
|
|
2676
2699
|
}
|
|
2677
2700
|
|
|
2678
2701
|
public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
|
|
@@ -3932,9 +3955,7 @@ export class ContainerRuntime
|
|
|
3932
3955
|
|
|
3933
3956
|
public notifyAttaching() {} // do nothing (deprecated method)
|
|
3934
3957
|
|
|
3935
|
-
public async getPendingLocalState(props?: {
|
|
3936
|
-
notifyImminentClosure: boolean;
|
|
3937
|
-
}): Promise<unknown> {
|
|
3958
|
+
public async getPendingLocalState(props?: IGetPendingLocalStateProps): Promise<unknown> {
|
|
3938
3959
|
return PerformanceEvent.timedExecAsync(
|
|
3939
3960
|
this.mc.logger,
|
|
3940
3961
|
{
|
|
@@ -3944,6 +3965,7 @@ export class ContainerRuntime
|
|
|
3944
3965
|
async (event) => {
|
|
3945
3966
|
this.verifyNotClosed();
|
|
3946
3967
|
const waitBlobsToAttach = props?.notifyImminentClosure;
|
|
3968
|
+
const stopBlobAttachingSignal = props?.stopBlobAttachingSignal;
|
|
3947
3969
|
if (this._orderSequentiallyCalls !== 0) {
|
|
3948
3970
|
throw new UsageError("can't get state during orderSequentially");
|
|
3949
3971
|
}
|
|
@@ -3952,7 +3974,7 @@ export class ContainerRuntime
|
|
|
3952
3974
|
// to close current batch.
|
|
3953
3975
|
this.flush();
|
|
3954
3976
|
const pendingAttachmentBlobs = waitBlobsToAttach
|
|
3955
|
-
? await this.blobManager.attachAndGetPendingBlobs()
|
|
3977
|
+
? await this.blobManager.attachAndGetPendingBlobs(stopBlobAttachingSignal)
|
|
3956
3978
|
: undefined;
|
|
3957
3979
|
const pending = this.pendingStateManager.getLocalState();
|
|
3958
3980
|
if (!pendingAttachmentBlobs && !this.hasPendingMessages()) {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -487,7 +487,16 @@ export abstract class FluidDataStoreContext
|
|
|
487
487
|
local: boolean,
|
|
488
488
|
localOpMetadata: unknown,
|
|
489
489
|
): void {
|
|
490
|
-
|
|
490
|
+
const safeTelemetryProps = extractSafePropertiesFromMessage(messageArg);
|
|
491
|
+
// On op process, tombstone error is logged in garbage collector. So, set "checkTombstone" to false when calling
|
|
492
|
+
// "verifyNotClosed" which logs tombstone errors. Throw error if tombstoned and throwing on load is configured.
|
|
493
|
+
this.verifyNotClosed("process", false /* checkTombstone */, safeTelemetryProps);
|
|
494
|
+
if (this.tombstoned && this.throwOnTombstoneUsage) {
|
|
495
|
+
throw new DataCorruptionError(
|
|
496
|
+
"Context is tombstoned! Call site [process]",
|
|
497
|
+
safeTelemetryProps,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
491
500
|
|
|
492
501
|
const innerContents = messageArg.contents as FluidDataStoreMessage;
|
|
493
502
|
const message = {
|
|
@@ -721,11 +730,17 @@ export abstract class FluidDataStoreContext
|
|
|
721
730
|
}
|
|
722
731
|
}
|
|
723
732
|
|
|
724
|
-
|
|
733
|
+
/**
|
|
734
|
+
* Submits the signal to be sent to other clients.
|
|
735
|
+
* @param type - Type of the signal.
|
|
736
|
+
* @param content - Content of the signal.
|
|
737
|
+
* @param targetClientId - When specified, the signal is only sent to the provided client id.
|
|
738
|
+
*/
|
|
739
|
+
public submitSignal(type: string, content: any, targetClientId?: string) {
|
|
725
740
|
this.verifyNotClosed("submitSignal");
|
|
726
741
|
|
|
727
742
|
assert(!!this.channel, 0x147 /* "Channel must exist on submitting signal" */);
|
|
728
|
-
return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
|
|
743
|
+
return this._containerRuntime.submitDataStoreSignal(this.id, type, content, targetClientId);
|
|
729
744
|
}
|
|
730
745
|
|
|
731
746
|
/**
|
|
@@ -1090,7 +1105,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1090
1105
|
return message;
|
|
1091
1106
|
}
|
|
1092
1107
|
|
|
1093
|
-
|
|
1108
|
+
private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
|
|
1094
1109
|
let snapshot = this.snapshotTree;
|
|
1095
1110
|
let attributes: ReadFluidDataStoreAttributes;
|
|
1096
1111
|
let isRootDataStore = false;
|
|
@@ -1123,6 +1138,10 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1123
1138
|
isRootDataStore,
|
|
1124
1139
|
snapshot,
|
|
1125
1140
|
};
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
|
|
1144
|
+
return this.initialSnapshotDetailsP;
|
|
1126
1145
|
}
|
|
1127
1146
|
|
|
1128
1147
|
/**
|
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);
|
|
@@ -101,8 +101,8 @@ export class DeltaManagerProxyBase
|
|
|
101
101
|
super.dispose();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
public submitSignal(content: any): void {
|
|
105
|
-
return this.deltaManager.submitSignal(content);
|
|
104
|
+
public submitSignal(content: any, targetClientId?: string): void {
|
|
105
|
+
return this.deltaManager.submitSignal(content, targetClientId);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
public flush(): void {
|
|
@@ -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/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
|
|
@@ -251,13 +252,17 @@ export interface IGarbageCollector {
|
|
|
251
252
|
getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
|
|
252
253
|
/** Called when the latest summary of the system has been refreshed. */
|
|
253
254
|
refreshLatestSummary(result: IRefreshSummaryResult): Promise<void>;
|
|
254
|
-
/**
|
|
255
|
+
/**
|
|
256
|
+
* Called when a node with the given path is updated. If the node is inactive or tombstoned, this will log an error
|
|
257
|
+
* or throw an error if failing on incorrect usage is configured.
|
|
258
|
+
*/
|
|
255
259
|
nodeUpdated(
|
|
256
260
|
nodePath: string,
|
|
257
261
|
reason: "Loaded" | "Changed",
|
|
258
262
|
timestampMs?: number,
|
|
259
263
|
packagePath?: readonly string[],
|
|
260
|
-
|
|
264
|
+
request?: IRequest,
|
|
265
|
+
headerData?: RuntimeHeaderData,
|
|
261
266
|
): void;
|
|
262
267
|
/** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
|
|
263
268
|
addedOutboundReference(fromNodePath: string, toNodePath: string): void;
|