@fluidframework/container-runtime 2.0.0-internal.7.0.0 → 2.0.0-internal.7.0.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/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/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 +15 -18
- 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.0.
|
|
3
|
+
"version": "2.0.0-internal.7.0.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.0.
|
|
39
|
-
"@fluidframework/container-definitions": ">=2.0.0-internal.7.0.
|
|
40
|
-
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.0.
|
|
41
|
-
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.0.
|
|
42
|
-
"@fluidframework/core-utils": ">=2.0.0-internal.7.0.
|
|
43
|
-
"@fluidframework/datastore": ">=2.0.0-internal.7.0.
|
|
44
|
-
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.0.
|
|
45
|
-
"@fluidframework/driver-utils": ">=2.0.0-internal.7.0.
|
|
38
|
+
"@fluid-internal/client-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
39
|
+
"@fluidframework/container-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
40
|
+
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
41
|
+
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
42
|
+
"@fluidframework/core-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
43
|
+
"@fluidframework/datastore": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
44
|
+
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
45
|
+
"@fluidframework/driver-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
46
46
|
"@fluidframework/protocol-definitions": "^3.0.0",
|
|
47
|
-
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.0.
|
|
48
|
-
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.0.
|
|
49
|
-
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.0.
|
|
47
|
+
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
48
|
+
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
49
|
+
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.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.0.
|
|
57
|
+
"@fluid-internal/stochastic-test-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
58
58
|
"@fluid-tools/benchmark": "^0.48.0",
|
|
59
59
|
"@fluid-tools/build-cli": "^0.24.0",
|
|
60
60
|
"@fluidframework/build-common": "^2.0.0",
|
|
61
61
|
"@fluidframework/build-tools": "^0.24.0",
|
|
62
62
|
"@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.6.3.0",
|
|
63
63
|
"@fluidframework/eslint-config-fluid": "^2.1.0",
|
|
64
|
-
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.0.
|
|
65
|
-
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.0.
|
|
64
|
+
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.0",
|
|
65
|
+
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.0.1 <2.0.0-internal.7.1.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",
|
|
@@ -109,9 +109,6 @@
|
|
|
109
109
|
"ClassDeclaration_SummaryCollection": {
|
|
110
110
|
"backCompat": false
|
|
111
111
|
},
|
|
112
|
-
"InterfaceDeclaration_ISummarizerRuntime": {
|
|
113
|
-
"backCompat": false
|
|
114
|
-
},
|
|
115
112
|
"InterfaceDeclaration_INackSummaryResult": {
|
|
116
113
|
"backCompat": false
|
|
117
114
|
},
|
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";
|
|
@@ -475,6 +474,7 @@ export interface RuntimeHeaderData {
|
|
|
475
474
|
wait?: boolean;
|
|
476
475
|
viaHandle?: boolean;
|
|
477
476
|
allowTombstone?: boolean;
|
|
477
|
+
allowInactive?: boolean;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
/** Default values for Runtime Headers */
|
|
@@ -482,6 +482,7 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
|
|
|
482
482
|
wait: true,
|
|
483
483
|
viaHandle: false,
|
|
484
484
|
allowTombstone: false,
|
|
485
|
+
allowInactive: false,
|
|
485
486
|
};
|
|
486
487
|
|
|
487
488
|
/**
|
|
@@ -1090,10 +1091,15 @@ export class ContainerRuntime
|
|
|
1090
1091
|
*/
|
|
1091
1092
|
private nextSummaryNumber: number;
|
|
1092
1093
|
|
|
1093
|
-
/**
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1094
|
+
/** If false, loading or using a Tombstoned object should merely log, not fail */
|
|
1095
|
+
public get gcTombstoneEnforcementAllowed(): boolean {
|
|
1096
|
+
return this.garbageCollector.tombstoneEnforcementAllowed;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/** If true, throw an error when a tombstone data store is used. */
|
|
1100
|
+
public get gcThrowOnTombstoneUsage(): boolean {
|
|
1101
|
+
return this.garbageCollector.throwOnTombstoneUsage;
|
|
1102
|
+
}
|
|
1097
1103
|
|
|
1098
1104
|
/**
|
|
1099
1105
|
* GUID to identify a document in telemetry
|
|
@@ -1240,11 +1246,6 @@ export class ContainerRuntime
|
|
|
1240
1246
|
// Later updates come through calls to setConnectionState.
|
|
1241
1247
|
this._connected = connected;
|
|
1242
1248
|
|
|
1243
|
-
this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
|
|
1244
|
-
metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
|
|
1245
|
-
this.runtimeOptions.gcOptions[gcTombstoneGenerationOptionName] /* current */,
|
|
1246
|
-
);
|
|
1247
|
-
|
|
1248
1249
|
this.mc.logger.sendTelemetryEvent({
|
|
1249
1250
|
eventName: "GCFeatureMatrix",
|
|
1250
1251
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -1726,7 +1727,10 @@ export class ContainerRuntime
|
|
|
1726
1727
|
}
|
|
1727
1728
|
: create404Response(request);
|
|
1728
1729
|
} else if (requestParser.pathParts.length > 0) {
|
|
1729
|
-
|
|
1730
|
+
// Differentiate between requesting the dataStore directly, or one of its children
|
|
1731
|
+
const requestForChild = !requestParser.isLeaf(1);
|
|
1732
|
+
const dataStore = await this.getDataStoreFromRequest(id, request, requestForChild);
|
|
1733
|
+
|
|
1730
1734
|
const subRequest = requestParser.createSubRequest(1);
|
|
1731
1735
|
// We always expect createSubRequest to include a leading slash, but asserting here to protect against
|
|
1732
1736
|
// unintentionally modifying the url if that changes.
|
|
@@ -1758,6 +1762,7 @@ export class ContainerRuntime
|
|
|
1758
1762
|
private async getDataStoreFromRequest(
|
|
1759
1763
|
id: string,
|
|
1760
1764
|
request: IRequest,
|
|
1765
|
+
requestForChild: boolean,
|
|
1761
1766
|
): Promise<IFluidDataStoreChannel> {
|
|
1762
1767
|
const headerData: RuntimeHeaderData = {};
|
|
1763
1768
|
if (typeof request.headers?.[RuntimeHeaders.wait] === "boolean") {
|
|
@@ -1769,24 +1774,36 @@ export class ContainerRuntime
|
|
|
1769
1774
|
if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
|
|
1770
1775
|
headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
|
|
1771
1776
|
}
|
|
1777
|
+
if (typeof request.headers?.[AllowInactiveRequestHeaderKey] === "boolean") {
|
|
1778
|
+
headerData.allowInactive = request.headers[AllowInactiveRequestHeaderKey];
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// We allow Tombstone requests for sub-DataStore objects
|
|
1782
|
+
if (requestForChild) {
|
|
1783
|
+
headerData.allowTombstone = true;
|
|
1784
|
+
}
|
|
1772
1785
|
|
|
1773
1786
|
await this.dataStores.waitIfPendingAlias(id);
|
|
1774
1787
|
const internalId = this.internalId(id);
|
|
1775
1788
|
const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
|
|
1776
|
-
const dataStoreChannel = await dataStoreContext.realize();
|
|
1777
1789
|
|
|
1778
1790
|
// Remove query params, leading and trailing slashes from the url. This is done to make sure the format is
|
|
1779
1791
|
// the same as GC nodes id.
|
|
1780
1792
|
const urlWithoutQuery = trimLeadingAndTrailingSlashes(request.url.split("?")[0]);
|
|
1793
|
+
// Get the initial snapshot details which contain the data store package path.
|
|
1794
|
+
const details = await dataStoreContext.getInitialSnapshotDetails();
|
|
1795
|
+
|
|
1796
|
+
// Note that this will throw if the data store is inactive or tombstoned and throwing on incorrect usage
|
|
1797
|
+
// is configured.
|
|
1781
1798
|
this.garbageCollector.nodeUpdated(
|
|
1782
1799
|
`/${urlWithoutQuery}`,
|
|
1783
1800
|
"Loaded",
|
|
1784
1801
|
undefined /* timestampMs */,
|
|
1785
|
-
|
|
1786
|
-
request
|
|
1802
|
+
details.pkg,
|
|
1803
|
+
request,
|
|
1804
|
+
headerData,
|
|
1787
1805
|
);
|
|
1788
|
-
|
|
1789
|
-
return dataStoreChannel;
|
|
1806
|
+
return dataStoreContext.realize();
|
|
1790
1807
|
}
|
|
1791
1808
|
|
|
1792
1809
|
/** Adds the container's metadata to the given summary tree. */
|
|
@@ -2430,6 +2447,12 @@ export class ContainerRuntime
|
|
|
2430
2447
|
"entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
|
|
2431
2448
|
);
|
|
2432
2449
|
}
|
|
2450
|
+
this.garbageCollector.nodeUpdated(
|
|
2451
|
+
`/${internalId}`,
|
|
2452
|
+
"Loaded",
|
|
2453
|
+
undefined /* timestampMs */,
|
|
2454
|
+
context.packagePath,
|
|
2455
|
+
);
|
|
2433
2456
|
return channel.entryPoint;
|
|
2434
2457
|
}
|
|
2435
2458
|
|
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
|
|