@fluidframework/container-runtime 2.1.0-274160 → 2.1.0-276985
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/README.md +14 -5
- package/container-runtime.test-files.tar +0 -0
- package/{lib → dist/blobManager}/blobManager.d.ts +9 -28
- package/dist/blobManager/blobManager.d.ts.map +1 -0
- package/dist/{blobManager.js → blobManager/blobManager.js} +23 -83
- package/dist/blobManager/blobManager.js.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.js +82 -0
- package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
- package/dist/blobManager/index.d.ts +7 -0
- package/dist/blobManager/index.d.ts.map +1 -0
- package/dist/blobManager/index.js +16 -0
- package/dist/blobManager/index.js.map +1 -0
- package/dist/channelCollection.d.ts +1 -0
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +23 -13
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +20 -7
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +82 -77
- 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 +7 -2
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.js +2 -2
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +9 -0
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +1 -0
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/messageTypes.d.ts +1 -0
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js +1 -0
- package/dist/messageTypes.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +4 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +8 -4
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +18 -16
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +17 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +43 -5
- package/dist/opLifecycle/remoteMessageProcessor.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/dist/pendingStateManager.d.ts +11 -7
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +34 -15
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +8 -0
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +2 -0
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js +1 -2
- package/dist/summary/index.js.map +1 -1
- package/dist/summary/orderedClientElection.d.ts +1 -0
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/runWhileConnectedCoordinator.d.ts +1 -0
- package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/summary/summarizer.d.ts +1 -0
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +1 -0
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +29 -0
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryCollection.d.ts +10 -0
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js +1 -0
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +8 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +3 -3
- package/dist/summary/summaryFormat.js.map +1 -1
- package/{dist → lib/blobManager}/blobManager.d.ts +9 -28
- package/lib/blobManager/blobManager.d.ts.map +1 -0
- package/lib/{blobManager.js → blobManager/blobManager.js} +21 -83
- package/lib/blobManager/blobManager.js.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.js +75 -0
- package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
- package/lib/blobManager/index.d.ts +7 -0
- package/lib/blobManager/index.d.ts.map +1 -0
- package/lib/blobManager/index.js +7 -0
- package/lib/blobManager/index.js.map +1 -0
- package/lib/channelCollection.d.ts +1 -0
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +23 -13
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +20 -7
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +31 -26
- 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 +7 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.js +2 -2
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +9 -0
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +1 -0
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/messageTypes.d.ts +1 -0
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js +1 -0
- package/lib/messageTypes.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +4 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +8 -4
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +18 -16
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +17 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +41 -3
- package/lib/opLifecycle/remoteMessageProcessor.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/lib/pendingStateManager.d.ts +11 -7
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +35 -16
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +8 -0
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +2 -0
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js +1 -1
- package/lib/summary/index.js.map +1 -1
- package/lib/summary/orderedClientElection.d.ts +1 -0
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/runWhileConnectedCoordinator.d.ts +1 -0
- package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
- package/lib/summary/summarizer.d.ts +1 -0
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +1 -0
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +29 -0
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryCollection.d.ts +10 -0
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js +1 -0
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +8 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +1 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +23 -23
- package/src/{blobManager.ts → blobManager/blobManager.ts} +38 -122
- package/src/blobManager/blobManagerSnapSum.ts +133 -0
- package/src/blobManager/index.ts +19 -0
- package/src/channelCollection.ts +23 -13
- package/src/containerRuntime.ts +57 -39
- package/src/dataStoreContext.ts +8 -2
- package/src/gc/garbageCollection.ts +2 -2
- package/src/gc/gcDefinitions.ts +9 -0
- package/src/index.ts +1 -1
- package/src/messageTypes.ts +1 -0
- package/src/opLifecycle/batchManager.ts +4 -0
- package/src/opLifecycle/outbox.ts +19 -21
- package/src/opLifecycle/remoteMessageProcessor.ts +63 -6
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +43 -20
- package/src/summary/documentSchema.ts +8 -0
- package/src/summary/index.ts +0 -1
- package/src/summary/orderedClientElection.ts +1 -0
- package/src/summary/runWhileConnectedCoordinator.ts +1 -0
- package/src/summary/summarizer.ts +1 -0
- package/src/summary/summarizerTypes.ts +29 -0
- package/src/summary/summaryCollection.ts +10 -0
- package/src/summary/summaryFormat.ts +9 -1
- package/dist/blobManager.d.ts.map +0 -1
- package/dist/blobManager.js.map +0 -1
- package/lib/blobManager.d.ts.map +0 -1
- package/lib/blobManager.js.map +0 -1
- /package/api-report/{container-runtime.alpha.api.md → container-runtime.legacy.alpha.api.md} +0 -0
|
@@ -21,7 +21,6 @@ import { assert, Deferred } from "@fluidframework/core-utils/internal";
|
|
|
21
21
|
import {
|
|
22
22
|
IDocumentStorageService,
|
|
23
23
|
ICreateBlobResponse,
|
|
24
|
-
ISnapshotTree,
|
|
25
24
|
ISequencedDocumentMessage,
|
|
26
25
|
} from "@fluidframework/driver-definitions/internal";
|
|
27
26
|
import { canRetryOnError, runWithRetry } from "@fluidframework/driver-utils/internal";
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
} from "@fluidframework/runtime-definitions/internal";
|
|
33
32
|
import {
|
|
34
33
|
FluidHandleBase,
|
|
35
|
-
SummaryTreeBuilder,
|
|
36
34
|
createResponseError,
|
|
37
35
|
generateHandleContextPath,
|
|
38
36
|
responseToException,
|
|
@@ -47,7 +45,14 @@ import {
|
|
|
47
45
|
} from "@fluidframework/telemetry-utils/internal";
|
|
48
46
|
import { v4 as uuid } from "uuid";
|
|
49
47
|
|
|
50
|
-
import { IBlobMetadata } from "
|
|
48
|
+
import { IBlobMetadata } from "../metadata.js";
|
|
49
|
+
|
|
50
|
+
import {
|
|
51
|
+
getStorageIds,
|
|
52
|
+
summarizeBlobManagerState,
|
|
53
|
+
toRedirectTable,
|
|
54
|
+
type IBlobManagerLoadInfo,
|
|
55
|
+
} from "./blobManagerSnapSum.js";
|
|
51
56
|
|
|
52
57
|
/**
|
|
53
58
|
* This class represents blob (long string)
|
|
@@ -87,15 +92,6 @@ export class BlobHandle extends FluidHandleBase<ArrayBufferLike> {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
/**
|
|
91
|
-
* Information from a snapshot needed to load BlobManager
|
|
92
|
-
* @alpha
|
|
93
|
-
*/
|
|
94
|
-
export interface IBlobManagerLoadInfo {
|
|
95
|
-
ids?: string[];
|
|
96
|
-
redirectTable?: [string, string][];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
95
|
// Restrict the IContainerRuntime interface to the subset required by BlobManager. This helps to make
|
|
100
96
|
// the contract explicit and reduces the amount of mocking required for tests.
|
|
101
97
|
export type IBlobManagerRuntime = Pick<
|
|
@@ -145,9 +141,9 @@ const stashedPendingBlobOverrides: Pick<
|
|
|
145
141
|
uploadTime: undefined,
|
|
146
142
|
} as const;
|
|
147
143
|
|
|
144
|
+
export const blobManagerBasePath = "_blobs" as const;
|
|
145
|
+
|
|
148
146
|
export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
149
|
-
public static readonly basePath = "_blobs";
|
|
150
|
-
private static readonly redirectTableBlobName = ".redirectTable";
|
|
151
147
|
private readonly mc: MonitoringContext;
|
|
152
148
|
|
|
153
149
|
/**
|
|
@@ -177,10 +173,10 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
177
173
|
private readonly routeContext: IFluidHandleContext;
|
|
178
174
|
private readonly getStorage: () => IDocumentStorageService;
|
|
179
175
|
// Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
|
|
180
|
-
// blobPath's format - `/<
|
|
176
|
+
// blobPath's format - `/<basePath>/<blobId>`.
|
|
181
177
|
private readonly blobRequested: (blobPath: string) => void;
|
|
182
178
|
// Called to check if a blob has been deleted by GC.
|
|
183
|
-
// blobPath's format - `/<
|
|
179
|
+
// blobPath's format - `/<basePath>/<blobId>`.
|
|
184
180
|
private readonly isBlobDeleted: (blobPath: string) => boolean;
|
|
185
181
|
private readonly runtime: IBlobManagerRuntime;
|
|
186
182
|
private readonly closeContainer: (error?: ICriticalContainerError) => void;
|
|
@@ -201,10 +197,10 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
201
197
|
*/
|
|
202
198
|
sendBlobAttachOp: (localId: string, storageId?: string) => void;
|
|
203
199
|
// Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
|
|
204
|
-
// blobPath's format - `/<
|
|
200
|
+
// blobPath's format - `/<basePath>/<blobId>`.
|
|
205
201
|
readonly blobRequested: (blobPath: string) => void;
|
|
206
202
|
// Called to check if a blob has been deleted by GC.
|
|
207
|
-
// blobPath's format - `/<
|
|
203
|
+
// blobPath's format - `/<basePath>/<blobId>`.
|
|
208
204
|
readonly isBlobDeleted: (blobPath: string) => boolean;
|
|
209
205
|
readonly runtime: IBlobManagerRuntime;
|
|
210
206
|
stashedBlobs: IPendingBlobs | undefined;
|
|
@@ -234,7 +230,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
234
230
|
namespace: "BlobManager",
|
|
235
231
|
});
|
|
236
232
|
|
|
237
|
-
this.redirectTable = this.
|
|
233
|
+
this.redirectTable = toRedirectTable(snapshot, this.mc.logger, this.runtime.attachState);
|
|
238
234
|
|
|
239
235
|
// Begin uploading stashed blobs from previous container instance
|
|
240
236
|
Object.entries(stashedBlobs ?? {}).forEach(([localId, entry]) => {
|
|
@@ -346,27 +342,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
346
342
|
);
|
|
347
343
|
}
|
|
348
344
|
|
|
349
|
-
/**
|
|
350
|
-
* Set of actual storage IDs (i.e., IDs that can be requested from storage). This will be empty if the container is
|
|
351
|
-
* detached or there are no (non-pending) attachment blobs in the document
|
|
352
|
-
*/
|
|
353
|
-
private get storageIds(): Set<string> {
|
|
354
|
-
const ids = new Set<string | undefined>(this.redirectTable.values());
|
|
355
|
-
|
|
356
|
-
// If we are detached, we will not have storage IDs, only undefined
|
|
357
|
-
const undefinedValueInTable = ids.delete(undefined);
|
|
358
|
-
|
|
359
|
-
// For a detached container, entries are inserted into the redirect table with an undefined storage ID.
|
|
360
|
-
// For an attached container, entries are inserted w/storage ID after the BlobAttach op round-trips.
|
|
361
|
-
assert(
|
|
362
|
-
!undefinedValueInTable ||
|
|
363
|
-
(this.runtime.attachState === AttachState.Detached && ids.size === 0),
|
|
364
|
-
0x382 /* 'redirectTable' must contain only undefined while detached / defined values while attached */,
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
return ids as Set<string>;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
345
|
public async getBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
371
346
|
// Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
|
|
372
347
|
// an error, failing the call.
|
|
@@ -418,7 +393,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
418
393
|
}
|
|
419
394
|
: undefined;
|
|
420
395
|
return new BlobHandle(
|
|
421
|
-
|
|
396
|
+
getGCNodePathFromBlobId(id),
|
|
422
397
|
this.routeContext,
|
|
423
398
|
async () => this.getBlob(id),
|
|
424
399
|
callback,
|
|
@@ -568,7 +543,8 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
568
543
|
if (!entry.opsent) {
|
|
569
544
|
this.sendBlobAttachOp(localId, response.id);
|
|
570
545
|
}
|
|
571
|
-
|
|
546
|
+
const storageIds = getStorageIds(this.redirectTable, this.runtime.attachState);
|
|
547
|
+
if (storageIds.has(response.id)) {
|
|
572
548
|
// The blob is de-duped. Set up a local ID to storage ID mapping and return the blob. Since this is
|
|
573
549
|
// an existing blob, we don't have to wait for the op to be ack'd since this step has already
|
|
574
550
|
// happened before and so, the server won't delete it.
|
|
@@ -661,74 +637,8 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
661
637
|
}
|
|
662
638
|
}
|
|
663
639
|
|
|
664
|
-
/**
|
|
665
|
-
* Reads blobs needed to load BlobManager from storage.
|
|
666
|
-
* @param blobsTree - Tree containing IDs of previously attached blobs. We
|
|
667
|
-
* look for the IDs in the blob entries of the tree since the both the r11s
|
|
668
|
-
* and SPO drivers replace the attachment types returned in snapshot() with blobs.
|
|
669
|
-
*/
|
|
670
|
-
public static async load(
|
|
671
|
-
blobsTree: ISnapshotTree | undefined,
|
|
672
|
-
tryFetchBlob: (id: string) => Promise<[string, string][]>,
|
|
673
|
-
): Promise<IBlobManagerLoadInfo> {
|
|
674
|
-
if (!blobsTree) {
|
|
675
|
-
return {};
|
|
676
|
-
}
|
|
677
|
-
let redirectTable;
|
|
678
|
-
const tableId = blobsTree.blobs[this.redirectTableBlobName];
|
|
679
|
-
if (tableId) {
|
|
680
|
-
redirectTable = await tryFetchBlob(tableId);
|
|
681
|
-
}
|
|
682
|
-
const ids = Object.entries(blobsTree.blobs)
|
|
683
|
-
.filter(([k, _]) => k !== this.redirectTableBlobName)
|
|
684
|
-
.map(([_, v]) => v);
|
|
685
|
-
return { ids, redirectTable };
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Load a set of previously attached blob IDs and redirect table from a previous snapshot.
|
|
690
|
-
*/
|
|
691
|
-
private load(snapshot: IBlobManagerLoadInfo): Map<string, string | undefined> {
|
|
692
|
-
this.mc.logger.sendTelemetryEvent({
|
|
693
|
-
eventName: "AttachmentBlobsLoaded",
|
|
694
|
-
count: snapshot.ids?.length ?? 0,
|
|
695
|
-
redirectTable: snapshot.redirectTable?.length,
|
|
696
|
-
});
|
|
697
|
-
const table = new Map<string, string | undefined>(snapshot.redirectTable);
|
|
698
|
-
if (snapshot.ids) {
|
|
699
|
-
const detached = this.runtime.attachState === AttachState.Detached;
|
|
700
|
-
// If we are detached, we don't have storage IDs yet, so set to undefined
|
|
701
|
-
// Otherwise, set identity (id -> id) entries
|
|
702
|
-
snapshot.ids.forEach((entry) => table.set(entry, detached ? undefined : entry));
|
|
703
|
-
}
|
|
704
|
-
return table;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
640
|
public summarize(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats {
|
|
708
|
-
|
|
709
|
-
const blobIds =
|
|
710
|
-
this.storageIds.size > 0
|
|
711
|
-
? Array.from(this.storageIds)
|
|
712
|
-
: Array.from(this.redirectTable.keys());
|
|
713
|
-
const builder = new SummaryTreeBuilder();
|
|
714
|
-
blobIds.forEach((blobId) => {
|
|
715
|
-
builder.addAttachment(blobId);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// Any non-identity entries in the table need to be saved in the summary
|
|
719
|
-
if (this.redirectTable.size > blobIds.length) {
|
|
720
|
-
builder.addBlob(
|
|
721
|
-
BlobManager.redirectTableBlobName,
|
|
722
|
-
// filter out identity entries
|
|
723
|
-
JSON.stringify(
|
|
724
|
-
Array.from(this.redirectTable.entries()).filter(
|
|
725
|
-
([localId, storageId]) => localId !== storageId,
|
|
726
|
-
),
|
|
727
|
-
),
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return builder.getSummaryTree();
|
|
641
|
+
return summarizeBlobManagerState(this.redirectTable, this.runtime.attachState);
|
|
732
642
|
}
|
|
733
643
|
|
|
734
644
|
/**
|
|
@@ -765,7 +675,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
765
675
|
|
|
766
676
|
/**
|
|
767
677
|
* Delete blobs with the given routes from the redirect table.
|
|
768
|
-
* The routes are GC nodes paths of format -`/<
|
|
678
|
+
* The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
|
|
769
679
|
* Deleting the blobs involves 2 steps:
|
|
770
680
|
* 1. The redirect table entry for the local ids are deleted.
|
|
771
681
|
* 2. If the storage ids corresponding to the deleted local ids are not in-use anymore, the redirect table entries
|
|
@@ -838,7 +748,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
838
748
|
this.mc.logger.sendErrorEvent(
|
|
839
749
|
{
|
|
840
750
|
eventName: "GC_Deleted_Blob_Requested",
|
|
841
|
-
pkg:
|
|
751
|
+
pkg: blobManagerBasePath,
|
|
842
752
|
},
|
|
843
753
|
error,
|
|
844
754
|
);
|
|
@@ -935,22 +845,28 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
|
|
|
935
845
|
}
|
|
936
846
|
|
|
937
847
|
/**
|
|
938
|
-
* For a blobId, returns its path in GC's graph. The node path is of the format `/<
|
|
848
|
+
* For a blobId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<blobId>`.
|
|
939
849
|
* This path must match the path of the blob handle returned by the createBlob API because blobs are marked
|
|
940
850
|
* referenced by storing these handles in a referenced DDS.
|
|
941
851
|
*/
|
|
942
|
-
|
|
943
|
-
return `/${BlobManager.basePath}/${blobId}`;
|
|
944
|
-
}
|
|
852
|
+
const getGCNodePathFromBlobId = (blobId: string) => `/${blobManagerBasePath}/${blobId}`;
|
|
945
853
|
|
|
946
854
|
/**
|
|
947
|
-
* For a given GC node path, return the blobId. The node path is of the format `/<
|
|
855
|
+
* For a given GC node path, return the blobId. The node path is of the format `/<basePath>/<blobId>`.
|
|
948
856
|
*/
|
|
949
|
-
|
|
857
|
+
const getBlobIdFromGCNodePath = (nodePath: string) => {
|
|
950
858
|
const pathParts = nodePath.split("/");
|
|
951
|
-
assert(
|
|
952
|
-
pathParts.length === 3 && pathParts[1] === BlobManager.basePath,
|
|
953
|
-
0x5bd /* Invalid blob node path */,
|
|
954
|
-
);
|
|
859
|
+
assert(areBlobPathParts(pathParts), 0x5bd /* Invalid blob node path */);
|
|
955
860
|
return pathParts[2];
|
|
956
|
-
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Returns whether a given path is for attachment blobs that are in the format - "/blobManagerBasePath/...".
|
|
865
|
+
*/
|
|
866
|
+
export const isBlobPath = (path: string): path is `/${typeof blobManagerBasePath}/${string}` =>
|
|
867
|
+
areBlobPathParts(path.split("/"));
|
|
868
|
+
|
|
869
|
+
export const areBlobPathParts = (
|
|
870
|
+
pathParts: string[],
|
|
871
|
+
): pathParts is ["", typeof blobManagerBasePath, string] =>
|
|
872
|
+
pathParts.length === 3 && pathParts[1] === blobManagerBasePath;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AttachState,
|
|
8
|
+
type IContainerContext,
|
|
9
|
+
} from "@fluidframework/container-definitions/internal";
|
|
10
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
11
|
+
import { readAndParse } from "@fluidframework/driver-utils/internal";
|
|
12
|
+
import type { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions/internal";
|
|
13
|
+
import { SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal";
|
|
14
|
+
import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Information from a snapshot needed to load BlobManager
|
|
18
|
+
* @legacy
|
|
19
|
+
* @alpha
|
|
20
|
+
*/
|
|
21
|
+
export interface IBlobManagerLoadInfo {
|
|
22
|
+
ids?: string[];
|
|
23
|
+
redirectTable?: [string, string][];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const redirectTableBlobName = ".redirectTable";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export const blobsTreeName = ".blobs";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reads blobs needed to load BlobManager from storage.
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
export const loadBlobManagerLoadInfo = async (
|
|
38
|
+
context: Pick<IContainerContext, "baseSnapshot" | "storage" | "attachState">,
|
|
39
|
+
): Promise<IBlobManagerLoadInfo> => loadV1(context);
|
|
40
|
+
|
|
41
|
+
const loadV1 = async (
|
|
42
|
+
context: Pick<IContainerContext, "baseSnapshot" | "storage" | "attachState">,
|
|
43
|
+
): Promise<IBlobManagerLoadInfo> => {
|
|
44
|
+
const blobsTree = context.baseSnapshot?.trees[blobsTreeName];
|
|
45
|
+
|
|
46
|
+
if (!blobsTree) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
let redirectTableEntries: [string, string][] = [];
|
|
50
|
+
const tableId = blobsTree.blobs[redirectTableBlobName];
|
|
51
|
+
if (tableId) {
|
|
52
|
+
redirectTableEntries = await readAndParse(context.storage, tableId);
|
|
53
|
+
}
|
|
54
|
+
const ids = Object.entries(blobsTree.blobs)
|
|
55
|
+
.filter(([k, _]) => k !== redirectTableBlobName)
|
|
56
|
+
.map(([_, v]) => v);
|
|
57
|
+
|
|
58
|
+
return { ids, redirectTable: redirectTableEntries };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const toRedirectTable = (
|
|
62
|
+
snapshot: IBlobManagerLoadInfo,
|
|
63
|
+
logger: ITelemetryLoggerExt,
|
|
64
|
+
attachState: AttachState,
|
|
65
|
+
): Map<string, string | undefined> => {
|
|
66
|
+
logger.sendTelemetryEvent({
|
|
67
|
+
eventName: "AttachmentBlobsLoaded",
|
|
68
|
+
count: snapshot.ids?.length ?? 0,
|
|
69
|
+
redirectTable: snapshot.redirectTable?.length,
|
|
70
|
+
});
|
|
71
|
+
const redirectTable = new Map<string, string | undefined>(snapshot.redirectTable);
|
|
72
|
+
const detached = attachState !== AttachState.Attached;
|
|
73
|
+
if (snapshot.ids) {
|
|
74
|
+
// If we are detached, we don't have storage IDs yet, so set to undefined
|
|
75
|
+
// Otherwise, set identity (id -> id) entries.
|
|
76
|
+
snapshot.ids.forEach((entry) => redirectTable.set(entry, detached ? undefined : entry));
|
|
77
|
+
}
|
|
78
|
+
return redirectTable;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const summarizeBlobManagerState = (
|
|
82
|
+
redirectTable: Map<string, string | undefined>,
|
|
83
|
+
attachState: AttachState,
|
|
84
|
+
): ISummaryTreeWithStats => summarizeV1(redirectTable, attachState);
|
|
85
|
+
|
|
86
|
+
const summarizeV1 = (
|
|
87
|
+
redirectTable: Map<string, string | undefined>,
|
|
88
|
+
attachState: AttachState,
|
|
89
|
+
): ISummaryTreeWithStats => {
|
|
90
|
+
const storageIds = getStorageIds(redirectTable, attachState);
|
|
91
|
+
|
|
92
|
+
// if storageIds is empty, it means we are detached and have only local IDs, or that there are no blobs attached
|
|
93
|
+
const blobIds =
|
|
94
|
+
storageIds.size > 0 ? Array.from(storageIds) : Array.from(redirectTable.keys());
|
|
95
|
+
const builder = new SummaryTreeBuilder();
|
|
96
|
+
blobIds.forEach((blobId) => {
|
|
97
|
+
builder.addAttachment(blobId);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Any non-identity entries in the table need to be saved in the summary
|
|
101
|
+
if (redirectTable.size > blobIds.length) {
|
|
102
|
+
builder.addBlob(
|
|
103
|
+
redirectTableBlobName,
|
|
104
|
+
// filter out identity entries
|
|
105
|
+
JSON.stringify(
|
|
106
|
+
Array.from(redirectTable.entries()).filter(
|
|
107
|
+
([localId, storageId]) => localId !== storageId,
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return builder.getSummaryTree();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const getStorageIds = (
|
|
117
|
+
redirectTable: Map<string, string | undefined>,
|
|
118
|
+
attachState: AttachState,
|
|
119
|
+
) => {
|
|
120
|
+
const ids = new Set<string | undefined>(redirectTable.values());
|
|
121
|
+
|
|
122
|
+
// If we are detached, we will not have storage IDs, only undefined
|
|
123
|
+
const undefinedValueInTable = ids.delete(undefined);
|
|
124
|
+
|
|
125
|
+
// For a detached container, entries are inserted into the redirect table with an undefined storage ID.
|
|
126
|
+
// For an attached container, entries are inserted w/storage ID after the BlobAttach op round-trips.
|
|
127
|
+
assert(
|
|
128
|
+
!undefinedValueInTable || (attachState === AttachState.Detached && ids.size === 0),
|
|
129
|
+
0x382 /* 'redirectTable' must contain only undefined while detached / defined values while attached */,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return ids as Set<string>;
|
|
133
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
BlobManager,
|
|
8
|
+
IPendingBlobs,
|
|
9
|
+
IBlobManagerRuntime,
|
|
10
|
+
IBlobManagerEvents,
|
|
11
|
+
blobManagerBasePath,
|
|
12
|
+
isBlobPath,
|
|
13
|
+
} from "./blobManager.js";
|
|
14
|
+
export {
|
|
15
|
+
loadBlobManagerLoadInfo,
|
|
16
|
+
IBlobManagerLoadInfo,
|
|
17
|
+
blobsTreeName,
|
|
18
|
+
redirectTableBlobName,
|
|
19
|
+
} from "./blobManagerSnapSum.js";
|
package/src/channelCollection.ts
CHANGED
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
extractSafePropertiesFromMessage,
|
|
71
71
|
tagCodeArtifacts,
|
|
72
72
|
} from "@fluidframework/telemetry-utils/internal";
|
|
73
|
+
import { v4 as uuid } from "uuid";
|
|
73
74
|
|
|
74
75
|
import {
|
|
75
76
|
DeletedResponseHeaderKey,
|
|
@@ -113,6 +114,7 @@ export enum RuntimeHeaders {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
/** True if a tombstoned object should be returned without erroring
|
|
117
|
+
* @legacy
|
|
116
118
|
* @alpha
|
|
117
119
|
*/
|
|
118
120
|
export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in the enum above, but avoiding the breaking change
|
|
@@ -610,20 +612,28 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
610
612
|
* Please note that above mentioned functions have the implementation they have (allowing #2) due to #1.
|
|
611
613
|
*/
|
|
612
614
|
protected createDataStoreId(): string {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (this.
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
615
|
+
/**
|
|
616
|
+
* There is currently a bug where certain data store ids such as "[" are getting converted to ASCII characters
|
|
617
|
+
* in the snapshot.
|
|
618
|
+
* So, return short ids only if explicitly enabled via feature flags. Else, return uuid();
|
|
619
|
+
*/
|
|
620
|
+
if (this.mc.config.getBoolean("Fluid.Runtime.UseShortIds") === true) {
|
|
621
|
+
// We use three non-overlapping namespaces:
|
|
622
|
+
// - detached state: even numbers
|
|
623
|
+
// - attached state: odd numbers
|
|
624
|
+
// - uuids
|
|
625
|
+
// In first two cases we will encode result as strings in more compact form.
|
|
626
|
+
if (this.parentContext.attachState === AttachState.Detached) {
|
|
627
|
+
// container is detached, only one client observes content, no way to hit collisions with other clients.
|
|
628
|
+
return encodeCompactIdToString(2 * this.contexts.size);
|
|
629
|
+
}
|
|
630
|
+
const id = this.parentContext.containerRuntime.generateDocumentUniqueId();
|
|
631
|
+
if (typeof id === "number") {
|
|
632
|
+
return encodeCompactIdToString(2 * id + 1);
|
|
633
|
+
}
|
|
634
|
+
return id;
|
|
625
635
|
}
|
|
626
|
-
return
|
|
636
|
+
return uuid();
|
|
627
637
|
}
|
|
628
638
|
|
|
629
639
|
public createDetachedDataStore(
|