@fluidframework/container-runtime 2.0.0-internal.7.1.0 → 2.0.0-internal.7.1.1
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 +2 -1
- package/dist/blobManager.d.ts +3 -6
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +17 -42
- package/dist/blobManager.js.map +1 -1
- package/dist/container-runtime-alpha.d.ts +4 -4
- package/dist/container-runtime-beta.d.ts +4 -4
- package/dist/container-runtime-public.d.ts +4 -4
- package/dist/container-runtime.d.ts +4 -4
- package/dist/containerRuntime.d.ts +5 -4
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +27 -6
- 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 +39 -34
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +0 -16
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +0 -48
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +12 -3
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +41 -18
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts +1 -0
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +12 -2
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +21 -7
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +12 -6
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +74 -42
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +1 -5
- package/dist/gc/index.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 -6
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +18 -43
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +5 -4
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +28 -7
- 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 +40 -35
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +0 -16
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +2 -50
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +12 -3
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +42 -19
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts +1 -0
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +14 -4
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +21 -7
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +12 -6
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +74 -42
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.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 +21 -17
- package/src/blobManager.ts +18 -58
- package/src/containerRuntime.ts +39 -16
- package/src/dataStoreContext.ts +17 -8
- package/src/dataStores.ts +2 -80
- package/src/gc/garbageCollection.ts +53 -24
- package/src/gc/gcConfigs.ts +22 -4
- package/src/gc/gcDefinitions.ts +22 -7
- package/src/gc/gcTelemetry.ts +103 -56
- package/src/gc/index.ts +0 -4
- package/src/packageVersion.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/container-runtime",
|
|
3
|
-
"version": "2.0.0-internal.7.1.
|
|
3
|
+
"version": "2.0.0-internal.7.1.1",
|
|
4
4
|
"description": "Fluid container runtime",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
"temp-directory": "nyc/.nyc_output"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@fluid-internal/client-utils": ">=2.0.0-internal.7.1.
|
|
39
|
-
"@fluidframework/container-definitions": ">=2.0.0-internal.7.1.
|
|
40
|
-
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.1.
|
|
41
|
-
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.1.
|
|
42
|
-
"@fluidframework/core-utils": ">=2.0.0-internal.7.1.
|
|
43
|
-
"@fluidframework/datastore": ">=2.0.0-internal.7.1.
|
|
44
|
-
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.1.
|
|
45
|
-
"@fluidframework/driver-utils": ">=2.0.0-internal.7.1.
|
|
38
|
+
"@fluid-internal/client-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
39
|
+
"@fluidframework/container-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
40
|
+
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
41
|
+
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
42
|
+
"@fluidframework/core-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
43
|
+
"@fluidframework/datastore": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
44
|
+
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
45
|
+
"@fluidframework/driver-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
46
46
|
"@fluidframework/protocol-definitions": "^3.0.0",
|
|
47
|
-
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.1.
|
|
48
|
-
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.1.
|
|
49
|
-
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.1.
|
|
47
|
+
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
48
|
+
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
49
|
+
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
50
50
|
"double-ended-queue": "^2.1.0-0",
|
|
51
51
|
"events": "^3.1.0",
|
|
52
52
|
"lz4js": "^0.2.0",
|
|
@@ -54,15 +54,15 @@
|
|
|
54
54
|
"uuid": "^9.0.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.1.
|
|
57
|
+
"@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
58
58
|
"@fluid-tools/benchmark": "^0.48.0",
|
|
59
59
|
"@fluid-tools/build-cli": "^0.25.0",
|
|
60
60
|
"@fluidframework/build-common": "^2.0.1",
|
|
61
61
|
"@fluidframework/build-tools": "^0.25.0",
|
|
62
|
-
"@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.
|
|
62
|
+
"@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.1.0",
|
|
63
63
|
"@fluidframework/eslint-config-fluid": "^3.0.0",
|
|
64
|
-
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.1.
|
|
65
|
-
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.1.
|
|
64
|
+
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
65
|
+
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.1.1 <2.0.0-internal.7.2.0",
|
|
66
66
|
"@microsoft/api-extractor": "^7.37.0",
|
|
67
67
|
"@types/double-ended-queue": "^2.1.0",
|
|
68
68
|
"@types/events": "^3.0.0",
|
|
@@ -83,7 +83,11 @@
|
|
|
83
83
|
"typescript": "~5.1.6"
|
|
84
84
|
},
|
|
85
85
|
"typeValidation": {
|
|
86
|
-
"broken": {
|
|
86
|
+
"broken": {
|
|
87
|
+
"ClassDeclaration_ContainerRuntime": {
|
|
88
|
+
"forwardCompat": false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
87
91
|
},
|
|
88
92
|
"scripts": {
|
|
89
93
|
"build": "fluid-build . --task build",
|
package/src/blobManager.ts
CHANGED
|
@@ -37,14 +37,8 @@ import {
|
|
|
37
37
|
ITelemetryContext,
|
|
38
38
|
} from "@fluidframework/runtime-definitions";
|
|
39
39
|
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
sendGCUnexpectedUsageEvent,
|
|
43
|
-
disableAttachmentBlobSweepKey,
|
|
44
|
-
throwOnTombstoneLoadKey,
|
|
45
|
-
} from "./gc";
|
|
40
|
+
import { disableAttachmentBlobSweepKey } from "./gc";
|
|
46
41
|
import { Throttler, formExponentialFn, IThrottler } from "./throttler";
|
|
47
|
-
import { summarizerClientType } from "./summary";
|
|
48
42
|
import { IBlobMetadata } from "./metadata";
|
|
49
43
|
|
|
50
44
|
/**
|
|
@@ -119,7 +113,6 @@ export type IBlobManagerRuntime = Pick<
|
|
|
119
113
|
IContainerRuntime,
|
|
120
114
|
"attachState" | "connected" | "logger" | "clientDetails"
|
|
121
115
|
> &
|
|
122
|
-
Pick<ContainerRuntime, "gcTombstoneEnforcementAllowed"> &
|
|
123
116
|
TypedEventEmitter<IContainerRuntimeEvents>;
|
|
124
117
|
|
|
125
118
|
type ICreateBlobResponseWithTTL = ICreateBlobResponse & Partial<Record<"minTTLInSeconds", number>>;
|
|
@@ -189,8 +182,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
189
182
|
),
|
|
190
183
|
);
|
|
191
184
|
|
|
192
|
-
/** If true, throw an error when a tombstone attachment blob is retrieved. */
|
|
193
|
-
private readonly throwOnTombstoneLoad: boolean;
|
|
194
185
|
/**
|
|
195
186
|
* This stores IDs of tombstoned blobs.
|
|
196
187
|
* Tombstone is a temporary feature that imitates a blob getting swept by garbage collection.
|
|
@@ -229,11 +220,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
229
220
|
logger: this.runtime.logger,
|
|
230
221
|
namespace: "BlobManager",
|
|
231
222
|
});
|
|
232
|
-
// Read the feature flag that tells whether to throw when a tombstone blob is requested.
|
|
233
|
-
this.throwOnTombstoneLoad =
|
|
234
|
-
this.mc.config.getBoolean(throwOnTombstoneLoadKey) === true &&
|
|
235
|
-
this.runtime.gcTombstoneEnforcementAllowed &&
|
|
236
|
-
this.runtime.clientDetails.type !== summarizerClientType;
|
|
237
223
|
|
|
238
224
|
this.redirectTable = this.load(snapshot);
|
|
239
225
|
|
|
@@ -376,14 +362,19 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
376
362
|
}
|
|
377
363
|
|
|
378
364
|
public async getBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
379
|
-
// Verify that the blob is
|
|
380
|
-
// failing the call.
|
|
381
|
-
this.
|
|
365
|
+
// Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
|
|
366
|
+
// an error, failing the call.
|
|
367
|
+
this.verifyBlobNotDeleted(blobId);
|
|
368
|
+
// Let runtime know that the corresponding GC node was requested.
|
|
369
|
+
// Note that this will throw if the blob is inactive or tombstoned and throwing on incorrect usage
|
|
370
|
+
// is configured.
|
|
371
|
+
this.blobRequested(getGCNodePathFromBlobId(blobId));
|
|
382
372
|
|
|
383
373
|
const pending = this.pendingBlobs.get(blobId);
|
|
384
374
|
if (pending) {
|
|
385
375
|
return pending.blob;
|
|
386
376
|
}
|
|
377
|
+
|
|
387
378
|
let storageId: string;
|
|
388
379
|
if (this.runtime.attachState === AttachState.Detached) {
|
|
389
380
|
assert(this.redirectTable.has(blobId), 0x383 /* requesting unknown blobs */);
|
|
@@ -397,9 +388,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
397
388
|
storageId = attachedStorageId;
|
|
398
389
|
}
|
|
399
390
|
|
|
400
|
-
// Let runtime know that the corresponding GC node was requested.
|
|
401
|
-
this.blobRequested(getGCNodePathFromBlobId(blobId));
|
|
402
|
-
|
|
403
391
|
return PerformanceEvent.timedExecAsync(
|
|
404
392
|
this.mc.logger,
|
|
405
393
|
{ eventName: "AttachmentReadBlob", id: storageId },
|
|
@@ -858,56 +846,28 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
858
846
|
}
|
|
859
847
|
|
|
860
848
|
/**
|
|
861
|
-
* Verifies that the blob with given id is
|
|
849
|
+
* Verifies that the blob with given id is not deleted, i.e., it has not been garbage collected. If the blob is GC'd,
|
|
862
850
|
* log an error and throw if necessary.
|
|
863
851
|
*/
|
|
864
|
-
private
|
|
865
|
-
|
|
866
|
-
* A blob can be in one of the following states:
|
|
867
|
-
* 1. "deleted" - It has been deleted by garbage collection sweep phase.
|
|
868
|
-
* 2. "tombstoned" - It is ready for deletion but sweep phase isn't enabled and tombstone feature is enabled.
|
|
869
|
-
* 3. "valid" - It has not been deleted or tombstoned.
|
|
870
|
-
*/
|
|
871
|
-
let state: "valid" | "tombstoned" | "deleted" = "valid";
|
|
872
|
-
if (this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
|
|
873
|
-
state = "deleted";
|
|
874
|
-
} else if (this.tombstonedBlobs.has(blobId)) {
|
|
875
|
-
state = "tombstoned";
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
if (state === "valid") {
|
|
852
|
+
private verifyBlobNotDeleted(blobId: string) {
|
|
853
|
+
if (!this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
|
|
879
854
|
return;
|
|
880
855
|
}
|
|
881
856
|
|
|
882
|
-
// If the blob is deleted or throw on tombstone load is enabled, throw an error which will fail any attempt
|
|
883
|
-
// to load the blob.
|
|
884
|
-
const shouldFail = state === "deleted" || this.throwOnTombstoneLoad;
|
|
885
857
|
const request = { url: blobId };
|
|
886
858
|
const error = responseToException(
|
|
887
|
-
createResponseError(
|
|
888
|
-
404,
|
|
889
|
-
"Blob was deleted",
|
|
890
|
-
request,
|
|
891
|
-
state === "tombstoned" ? { [TombstoneResponseHeaderKey]: true } : undefined,
|
|
892
|
-
),
|
|
859
|
+
createResponseError(404, `Blob was deleted`, request),
|
|
893
860
|
request,
|
|
894
861
|
);
|
|
895
|
-
|
|
896
|
-
|
|
862
|
+
// Only log deleted events. Tombstone events are logged by garbage collector.
|
|
863
|
+
this.mc.logger.sendErrorEvent(
|
|
897
864
|
{
|
|
898
|
-
eventName:
|
|
899
|
-
|
|
900
|
-
? "GC_Tombstone_Blob_Requested"
|
|
901
|
-
: "GC_Deleted_Blob_Requested",
|
|
902
|
-
category: shouldFail ? "error" : "generic",
|
|
903
|
-
gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
|
|
865
|
+
eventName: "GC_Deleted_Blob_Requested",
|
|
866
|
+
pkg: BlobManager.basePath,
|
|
904
867
|
},
|
|
905
|
-
[BlobManager.basePath],
|
|
906
868
|
error,
|
|
907
869
|
);
|
|
908
|
-
|
|
909
|
-
throw error;
|
|
910
|
-
}
|
|
870
|
+
throw error;
|
|
911
871
|
}
|
|
912
872
|
|
|
913
873
|
public setRedirectTable(table: Map<string, string>) {
|
package/src/containerRuntime.ts
CHANGED
|
@@ -166,7 +166,6 @@ import {
|
|
|
166
166
|
IGarbageCollector,
|
|
167
167
|
IGCRuntimeOptions,
|
|
168
168
|
IGCStats,
|
|
169
|
-
shouldAllowGcTombstoneEnforcement,
|
|
170
169
|
trimLeadingAndTrailingSlashes,
|
|
171
170
|
} from "./gc";
|
|
172
171
|
import { channelToDataStore, IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
|
|
@@ -478,6 +477,7 @@ export interface RuntimeHeaderData {
|
|
|
478
477
|
wait?: boolean;
|
|
479
478
|
viaHandle?: boolean;
|
|
480
479
|
allowTombstone?: boolean;
|
|
480
|
+
allowInactive?: boolean;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
/** Default values for Runtime Headers */
|
|
@@ -485,6 +485,7 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
|
|
|
485
485
|
wait: true,
|
|
486
486
|
viaHandle: false,
|
|
487
487
|
allowTombstone: false,
|
|
488
|
+
allowInactive: false,
|
|
488
489
|
};
|
|
489
490
|
|
|
490
491
|
/**
|
|
@@ -1140,10 +1141,15 @@ export class ContainerRuntime
|
|
|
1140
1141
|
*/
|
|
1141
1142
|
private nextSummaryNumber: number;
|
|
1142
1143
|
|
|
1143
|
-
/**
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1144
|
+
/** If false, loading or using a Tombstoned object should merely log, not fail */
|
|
1145
|
+
public get gcTombstoneEnforcementAllowed(): boolean {
|
|
1146
|
+
return this.garbageCollector.tombstoneEnforcementAllowed;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/** If true, throw an error when a tombstone data store is used. */
|
|
1150
|
+
public get gcThrowOnTombstoneUsage(): boolean {
|
|
1151
|
+
return this.garbageCollector.throwOnTombstoneUsage;
|
|
1152
|
+
}
|
|
1147
1153
|
|
|
1148
1154
|
/**
|
|
1149
1155
|
* GUID to identify a document in telemetry
|
|
@@ -1290,11 +1296,6 @@ export class ContainerRuntime
|
|
|
1290
1296
|
// Later updates come through calls to setConnectionState.
|
|
1291
1297
|
this._connected = connected;
|
|
1292
1298
|
|
|
1293
|
-
this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
|
|
1294
|
-
metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
|
|
1295
|
-
this.runtimeOptions.gcOptions[gcTombstoneGenerationOptionName] /* current */,
|
|
1296
|
-
);
|
|
1297
|
-
|
|
1298
1299
|
this.mc.logger.sendTelemetryEvent({
|
|
1299
1300
|
eventName: "GCFeatureMatrix",
|
|
1300
1301
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -1778,7 +1779,10 @@ export class ContainerRuntime
|
|
|
1778
1779
|
}
|
|
1779
1780
|
: create404Response(request);
|
|
1780
1781
|
} else if (requestParser.pathParts.length > 0) {
|
|
1781
|
-
|
|
1782
|
+
// Differentiate between requesting the dataStore directly, or one of its children
|
|
1783
|
+
const requestForChild = !requestParser.isLeaf(1);
|
|
1784
|
+
const dataStore = await this.getDataStoreFromRequest(id, request, requestForChild);
|
|
1785
|
+
|
|
1782
1786
|
const subRequest = requestParser.createSubRequest(1);
|
|
1783
1787
|
// We always expect createSubRequest to include a leading slash, but asserting here to protect against
|
|
1784
1788
|
// unintentionally modifying the url if that changes.
|
|
@@ -1811,6 +1815,7 @@ export class ContainerRuntime
|
|
|
1811
1815
|
private async getDataStoreFromRequest(
|
|
1812
1816
|
id: string,
|
|
1813
1817
|
request: IRequest,
|
|
1818
|
+
requestForChild: boolean,
|
|
1814
1819
|
): Promise<IFluidDataStoreChannel> {
|
|
1815
1820
|
const headerData: RuntimeHeaderData = {};
|
|
1816
1821
|
if (typeof request.headers?.[RuntimeHeaders.wait] === "boolean") {
|
|
@@ -1822,24 +1827,36 @@ export class ContainerRuntime
|
|
|
1822
1827
|
if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
|
|
1823
1828
|
headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
|
|
1824
1829
|
}
|
|
1830
|
+
if (typeof request.headers?.[AllowInactiveRequestHeaderKey] === "boolean") {
|
|
1831
|
+
headerData.allowInactive = request.headers[AllowInactiveRequestHeaderKey];
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// We allow Tombstone requests for sub-DataStore objects
|
|
1835
|
+
if (requestForChild) {
|
|
1836
|
+
headerData.allowTombstone = true;
|
|
1837
|
+
}
|
|
1825
1838
|
|
|
1826
1839
|
await this.dataStores.waitIfPendingAlias(id);
|
|
1827
1840
|
const internalId = this.internalId(id);
|
|
1828
1841
|
const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
|
|
1829
|
-
const dataStoreChannel = await dataStoreContext.realize();
|
|
1830
1842
|
|
|
1831
1843
|
// Remove query params, leading and trailing slashes from the url. This is done to make sure the format is
|
|
1832
1844
|
// the same as GC nodes id.
|
|
1833
1845
|
const urlWithoutQuery = trimLeadingAndTrailingSlashes(request.url.split("?")[0]);
|
|
1846
|
+
// Get the initial snapshot details which contain the data store package path.
|
|
1847
|
+
const details = await dataStoreContext.getInitialSnapshotDetails();
|
|
1848
|
+
|
|
1849
|
+
// Note that this will throw if the data store is inactive or tombstoned and throwing on incorrect usage
|
|
1850
|
+
// is configured.
|
|
1834
1851
|
this.garbageCollector.nodeUpdated(
|
|
1835
1852
|
`/${urlWithoutQuery}`,
|
|
1836
1853
|
"Loaded",
|
|
1837
1854
|
undefined /* timestampMs */,
|
|
1838
|
-
|
|
1839
|
-
request
|
|
1855
|
+
details.pkg,
|
|
1856
|
+
request,
|
|
1857
|
+
headerData,
|
|
1840
1858
|
);
|
|
1841
|
-
|
|
1842
|
-
return dataStoreChannel;
|
|
1859
|
+
return dataStoreContext.realize();
|
|
1843
1860
|
}
|
|
1844
1861
|
|
|
1845
1862
|
/** Adds the container's metadata to the given summary tree. */
|
|
@@ -2483,6 +2500,12 @@ export class ContainerRuntime
|
|
|
2483
2500
|
"entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
|
|
2484
2501
|
);
|
|
2485
2502
|
}
|
|
2503
|
+
this.garbageCollector.nodeUpdated(
|
|
2504
|
+
`/${internalId}`,
|
|
2505
|
+
"Loaded",
|
|
2506
|
+
undefined /* timestampMs */,
|
|
2507
|
+
context.packagePath,
|
|
2508
|
+
);
|
|
2486
2509
|
return channel.entryPoint;
|
|
2487
2510
|
}
|
|
2488
2511
|
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
summarizerClientType,
|
|
79
79
|
} from "./summary";
|
|
80
80
|
import { ContainerRuntime } from "./containerRuntime";
|
|
81
|
-
import { sendGCUnexpectedUsageEvent
|
|
81
|
+
import { sendGCUnexpectedUsageEvent } from "./gc";
|
|
82
82
|
|
|
83
83
|
function createAttributes(
|
|
84
84
|
pkg: readonly string[],
|
|
@@ -325,11 +325,7 @@ export abstract class FluidDataStoreContext
|
|
|
325
325
|
this.mc.logger,
|
|
326
326
|
);
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
this.throwOnTombstoneUsage =
|
|
330
|
-
this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
|
|
331
|
-
this._containerRuntime.gcTombstoneEnforcementAllowed &&
|
|
332
|
-
this.clientDetails.type !== summarizerClientType;
|
|
328
|
+
this.throwOnTombstoneUsage = this._containerRuntime.gcThrowOnTombstoneUsage;
|
|
333
329
|
|
|
334
330
|
// By default, a data store can log maximum 10 local changes telemetry in summarizer.
|
|
335
331
|
this.localChangesTelemetryCount =
|
|
@@ -486,7 +482,16 @@ export abstract class FluidDataStoreContext
|
|
|
486
482
|
local: boolean,
|
|
487
483
|
localOpMetadata: unknown,
|
|
488
484
|
): void {
|
|
489
|
-
|
|
485
|
+
const safeTelemetryProps = extractSafePropertiesFromMessage(messageArg);
|
|
486
|
+
// On op process, tombstone error is logged in garbage collector. So, set "checkTombstone" to false when calling
|
|
487
|
+
// "verifyNotClosed" which logs tombstone errors. Throw error if tombstoned and throwing on load is configured.
|
|
488
|
+
this.verifyNotClosed("process", false /* checkTombstone */, safeTelemetryProps);
|
|
489
|
+
if (this.tombstoned && this.throwOnTombstoneUsage) {
|
|
490
|
+
throw new DataCorruptionError(
|
|
491
|
+
"Context is tombstoned! Call site [process]",
|
|
492
|
+
safeTelemetryProps,
|
|
493
|
+
);
|
|
494
|
+
}
|
|
490
495
|
|
|
491
496
|
const innerContents = messageArg.contents as FluidDataStoreMessage;
|
|
492
497
|
const message = {
|
|
@@ -1089,7 +1094,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1089
1094
|
return message;
|
|
1090
1095
|
}
|
|
1091
1096
|
|
|
1092
|
-
|
|
1097
|
+
private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
|
|
1093
1098
|
let snapshot = this.snapshotTree;
|
|
1094
1099
|
let attributes: ReadFluidDataStoreAttributes;
|
|
1095
1100
|
let isRootDataStore = false;
|
|
@@ -1122,6 +1127,10 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1122
1127
|
isRootDataStore,
|
|
1123
1128
|
snapshot,
|
|
1124
1129
|
};
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
|
|
1133
|
+
return this.initialSnapshotDetailsP;
|
|
1125
1134
|
}
|
|
1126
1135
|
|
|
1127
1136
|
/**
|
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,
|
|
@@ -65,12 +60,7 @@ import {
|
|
|
65
60
|
} from "./dataStoreContext";
|
|
66
61
|
import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs";
|
|
67
62
|
import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
|
|
68
|
-
import {
|
|
69
|
-
GCNodeType,
|
|
70
|
-
disableDatastoreSweepKey,
|
|
71
|
-
throwOnTombstoneLoadKey,
|
|
72
|
-
sendGCUnexpectedUsageEvent,
|
|
73
|
-
} from "./gc";
|
|
63
|
+
import { GCNodeType, disableDatastoreSweepKey, sendGCUnexpectedUsageEvent } from "./gc";
|
|
74
64
|
import {
|
|
75
65
|
summarizerClientType,
|
|
76
66
|
IContainerRuntimeMetadata,
|
|
@@ -104,8 +94,6 @@ export class DataStores implements IDisposable {
|
|
|
104
94
|
// Stores the ids of new data stores between two GC runs. This is used to notify the garbage collector of new
|
|
105
95
|
// root data stores that are added.
|
|
106
96
|
private dataStoresSinceLastGC: string[] = [];
|
|
107
|
-
/** If true, throw an error when a tombstone data store is retrieved. */
|
|
108
|
-
private readonly throwOnTombstoneLoad: boolean;
|
|
109
97
|
// The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
|
|
110
98
|
// the container runtime to other nodes.
|
|
111
99
|
private readonly containerRuntimeHandle: IFluidHandle;
|
|
@@ -140,12 +128,6 @@ export class DataStores implements IDisposable {
|
|
|
140
128
|
this.runtime.IFluidHandleContext,
|
|
141
129
|
);
|
|
142
130
|
|
|
143
|
-
// Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
|
|
144
|
-
this.throwOnTombstoneLoad =
|
|
145
|
-
this.mc.config.getBoolean(throwOnTombstoneLoadKey) === true &&
|
|
146
|
-
this.runtime.gcTombstoneEnforcementAllowed &&
|
|
147
|
-
this.runtime.clientDetails.type !== summarizerClientType;
|
|
148
|
-
|
|
149
131
|
// Extract stores stored inside the snapshot
|
|
150
132
|
const fluidDataStores = new Map<string, ISnapshotTree>();
|
|
151
133
|
if (baseSnapshot) {
|
|
@@ -455,9 +437,6 @@ export class DataStores implements IDisposable {
|
|
|
455
437
|
const request: IRequest = { url: id };
|
|
456
438
|
throw responseToException(create404Response(request), request);
|
|
457
439
|
}
|
|
458
|
-
|
|
459
|
-
this.validateNotTombstoned(context, requestHeaderData);
|
|
460
|
-
|
|
461
440
|
return context;
|
|
462
441
|
}
|
|
463
442
|
|
|
@@ -477,8 +456,6 @@ export class DataStores implements IDisposable {
|
|
|
477
456
|
if (context === undefined) {
|
|
478
457
|
return undefined;
|
|
479
458
|
}
|
|
480
|
-
// Check if the data store is tombstoned. If so, we want to log a telemetry event.
|
|
481
|
-
this.checkIfTombstoned(context, requestHeaderData);
|
|
482
459
|
return context;
|
|
483
460
|
}
|
|
484
461
|
|
|
@@ -529,61 +506,6 @@ export class DataStores implements IDisposable {
|
|
|
529
506
|
}
|
|
530
507
|
}
|
|
531
508
|
|
|
532
|
-
/**
|
|
533
|
-
* Checks if the data store has not been marked as tombstone by GC or not.
|
|
534
|
-
* @param context - the data store context in question
|
|
535
|
-
* @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
|
|
536
|
-
* @returns true if the data store is tombstoned. Otherwise, returns false.
|
|
537
|
-
*/
|
|
538
|
-
private checkIfTombstoned(
|
|
539
|
-
context: FluidDataStoreContext,
|
|
540
|
-
requestHeaderData: RuntimeHeaderData,
|
|
541
|
-
) {
|
|
542
|
-
if (!context.tombstoned) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
const logErrorEvent = this.throwOnTombstoneLoad && !requestHeaderData.allowTombstone;
|
|
546
|
-
sendGCUnexpectedUsageEvent(
|
|
547
|
-
this.mc,
|
|
548
|
-
{
|
|
549
|
-
eventName: "GC_Tombstone_DataStore_Requested",
|
|
550
|
-
category: logErrorEvent ? "error" : "generic",
|
|
551
|
-
isSummarizerClient: this.runtime.clientDetails.type === summarizerClientType,
|
|
552
|
-
id: context.id,
|
|
553
|
-
headers: JSON.stringify(requestHeaderData),
|
|
554
|
-
gcTombstoneEnforcementAllowed: this.runtime.gcTombstoneEnforcementAllowed,
|
|
555
|
-
},
|
|
556
|
-
context.isLoaded ? context.packagePath : undefined,
|
|
557
|
-
);
|
|
558
|
-
return true;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Validates that the data store context requested has not been marked as tombstone by GC.
|
|
563
|
-
* @param context - the data store context in question
|
|
564
|
-
* @param request - the request information to log if the validation detects the data store has been tombstoned
|
|
565
|
-
* @param requestHeaderData - the request header information to log if the validation detects the data store has been tombstoned
|
|
566
|
-
*/
|
|
567
|
-
private validateNotTombstoned(
|
|
568
|
-
context: FluidDataStoreContext,
|
|
569
|
-
requestHeaderData: RuntimeHeaderData,
|
|
570
|
-
) {
|
|
571
|
-
if (this.checkIfTombstoned(context, requestHeaderData)) {
|
|
572
|
-
// The requested data store is removed by gc. Create a 404 gc response exception.
|
|
573
|
-
const request: IRequest = { url: context.id };
|
|
574
|
-
const error = responseToException(
|
|
575
|
-
createResponseError(404, "DataStore was deleted", request, {
|
|
576
|
-
[TombstoneResponseHeaderKey]: true,
|
|
577
|
-
}),
|
|
578
|
-
request,
|
|
579
|
-
);
|
|
580
|
-
// Throw an error if configured via options and via request headers.
|
|
581
|
-
if (this.throwOnTombstoneLoad && !requestHeaderData.allowTombstone) {
|
|
582
|
-
throw error;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
509
|
public processSignal(fluidDataStoreId: string, message: IInboundSignalMessage, local: boolean) {
|
|
588
510
|
this.validateNotDeleted(fluidDataStoreId);
|
|
589
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";
|
|
@@ -113,6 +113,19 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
113
113
|
private readonly summaryStateTracker: GCSummaryStateTracker;
|
|
114
114
|
private readonly telemetryTracker: GCTelemetryTracker;
|
|
115
115
|
|
|
116
|
+
/** If false, loading or using a Tombstoned object should merely log, not fail */
|
|
117
|
+
public get tombstoneEnforcementAllowed(): boolean {
|
|
118
|
+
return this.configs.tombstoneEnforcementAllowed;
|
|
119
|
+
}
|
|
120
|
+
/** If true, throw an error when a tombstone data store is retrieved */
|
|
121
|
+
public get throwOnTombstoneLoad(): boolean {
|
|
122
|
+
return this.configs.throwOnTombstoneLoad;
|
|
123
|
+
}
|
|
124
|
+
/** If true, throw an error when a tombstone data store is used */
|
|
125
|
+
public get throwOnTombstoneUsage(): boolean {
|
|
126
|
+
return this.configs.throwOnTombstoneUsage;
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
/** For a given node path, returns the node's package path. */
|
|
117
130
|
private readonly getNodePackagePath: (
|
|
118
131
|
nodePath: string,
|
|
@@ -176,7 +189,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
176
189
|
this.mc,
|
|
177
190
|
this.configs,
|
|
178
191
|
this.isSummarizerClient,
|
|
179
|
-
this.runtime.gcTombstoneEnforcementAllowed,
|
|
180
192
|
createParams.createContainerMetadata,
|
|
181
193
|
(nodeId: string) => this.runtime.getNodeType(nodeId),
|
|
182
194
|
(nodeId: string) => this.unreferencedNodesState.get(nodeId),
|
|
@@ -850,11 +862,13 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
850
862
|
}
|
|
851
863
|
|
|
852
864
|
/**
|
|
853
|
-
* 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.
|
|
854
867
|
* @param nodePath - The path of the node that changed.
|
|
855
868
|
* @param reason - Whether the node was loaded or changed.
|
|
856
869
|
* @param timestampMs - The timestamp when the node changed.
|
|
857
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.
|
|
858
872
|
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
859
873
|
*/
|
|
860
874
|
public nodeUpdated(
|
|
@@ -862,12 +876,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
862
876
|
reason: "Loaded" | "Changed",
|
|
863
877
|
timestampMs?: number,
|
|
864
878
|
packagePath?: readonly string[],
|
|
865
|
-
|
|
879
|
+
request?: IRequest,
|
|
880
|
+
headerData?: RuntimeHeaderData,
|
|
866
881
|
) {
|
|
867
882
|
if (!this.configs.shouldRunGC) {
|
|
868
883
|
return;
|
|
869
884
|
}
|
|
870
885
|
|
|
886
|
+
const isTombstoned = this.tombstones.includes(nodePath);
|
|
887
|
+
|
|
871
888
|
// This will log if appropriate
|
|
872
889
|
this.telemetryTracker.nodeUsed({
|
|
873
890
|
id: nodePath,
|
|
@@ -876,32 +893,44 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
876
893
|
timestampMs ?? this.runtime.getCurrentReferenceTimestampMs(),
|
|
877
894
|
packagePath,
|
|
878
895
|
completedGCRuns: this.completedRuns,
|
|
879
|
-
isTombstoned
|
|
896
|
+
isTombstoned,
|
|
880
897
|
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
881
|
-
|
|
898
|
+
headers: headerData,
|
|
882
899
|
});
|
|
883
900
|
|
|
884
|
-
|
|
885
|
-
|
|
901
|
+
const nodeType = this.runtime.getNodeType(nodePath);
|
|
902
|
+
|
|
903
|
+
// Unless this is a Loaded event for a Blob or DataStore, we're done after telemetry tracking
|
|
904
|
+
if (reason !== "Loaded" || ![GCNodeType.Blob, GCNodeType.DataStore].includes(nodeType)) {
|
|
886
905
|
return;
|
|
887
906
|
}
|
|
888
907
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
if (shouldThrowOnInactiveLoad && state === "Inactive") {
|
|
897
|
-
const request: IRequest = { url: nodePath };
|
|
898
|
-
const error = responseToException(
|
|
899
|
-
createResponseError(404, "Object is inactive", request, {
|
|
900
|
-
[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,
|
|
901
915
|
}),
|
|
902
|
-
|
|
916
|
+
errorRequest,
|
|
903
917
|
);
|
|
904
|
-
|
|
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
|
+
}
|
|
905
934
|
}
|
|
906
935
|
}
|
|
907
936
|
|