@fluidframework/container-runtime 2.33.2 → 2.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/api-report/container-runtime.legacy.alpha.api.md +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +31 -8
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +90 -17
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +26 -10
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +42 -11
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +19 -10
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +39 -32
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +29 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +139 -149
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +15 -16
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +37 -19
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -9
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -0
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +20 -7
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +16 -20
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +22 -8
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +11 -16
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +31 -8
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +91 -18
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +26 -10
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +43 -12
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +19 -10
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +36 -29
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +29 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +60 -70
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +15 -16
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +38 -20
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +1 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -3
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -0
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +20 -7
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +16 -20
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +22 -8
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +11 -16
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +18 -18
- package/src/blobManager/blobManager.ts +141 -33
- package/src/channelCollection.ts +77 -19
- package/src/compatUtils.ts +53 -30
- package/src/containerRuntime.ts +102 -81
- package/src/dataStoreContext.ts +48 -38
- package/src/index.ts +1 -13
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/outbox.ts +42 -33
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +37 -20
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
TypedEventEmitter,
|
|
8
|
-
bufferToString,
|
|
9
|
-
stringToBuffer,
|
|
10
|
-
} from "@fluid-internal/client-utils";
|
|
6
|
+
import { bufferToString, createEmitter, stringToBuffer } from "@fluid-internal/client-utils";
|
|
11
7
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
12
8
|
import {
|
|
13
9
|
IContainerRuntime,
|
|
14
10
|
IContainerRuntimeEvents,
|
|
15
11
|
} from "@fluidframework/container-runtime-definitions/internal";
|
|
16
12
|
import type {
|
|
17
|
-
|
|
13
|
+
IEmitter,
|
|
18
14
|
IEventProvider,
|
|
19
15
|
IFluidHandleContext,
|
|
20
16
|
IFluidHandleInternal,
|
|
21
17
|
IFluidHandleInternalPayloadPending,
|
|
18
|
+
ILocalFluidHandle,
|
|
19
|
+
ILocalFluidHandleEvents,
|
|
20
|
+
Listenable,
|
|
21
|
+
PayloadState,
|
|
22
22
|
} from "@fluidframework/core-interfaces/internal";
|
|
23
23
|
import { assert, Deferred } from "@fluidframework/core-utils/internal";
|
|
24
24
|
import {
|
|
@@ -65,7 +65,9 @@ import {
|
|
|
65
65
|
*/
|
|
66
66
|
export class BlobHandle
|
|
67
67
|
extends FluidHandleBase<ArrayBufferLike>
|
|
68
|
-
implements
|
|
68
|
+
implements
|
|
69
|
+
ILocalFluidHandle<ArrayBufferLike>,
|
|
70
|
+
IFluidHandleInternalPayloadPending<ArrayBufferLike>
|
|
69
71
|
{
|
|
70
72
|
private attached: boolean = false;
|
|
71
73
|
|
|
@@ -73,9 +75,30 @@ export class BlobHandle
|
|
|
73
75
|
return this.routeContext.isAttached && this.attached;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
private _events:
|
|
79
|
+
| (Listenable<ILocalFluidHandleEvents> & IEmitter<ILocalFluidHandleEvents>)
|
|
80
|
+
| undefined;
|
|
81
|
+
public get events(): Listenable<ILocalFluidHandleEvents> {
|
|
82
|
+
return (this._events ??= createEmitter<ILocalFluidHandleEvents>());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private _state: PayloadState = "pending";
|
|
86
|
+
public get payloadState(): PayloadState {
|
|
87
|
+
return this._state;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The error property starts undefined, signalling that there has been no error yet.
|
|
92
|
+
* If an error occurs, the property will contain the error.
|
|
93
|
+
*/
|
|
94
|
+
private _payloadShareError: unknown;
|
|
95
|
+
public get payloadShareError(): unknown {
|
|
96
|
+
return this._payloadShareError;
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
public readonly absolutePath: string;
|
|
77
100
|
|
|
78
|
-
constructor(
|
|
101
|
+
public constructor(
|
|
79
102
|
public readonly path: string,
|
|
80
103
|
public readonly routeContext: IFluidHandleContext,
|
|
81
104
|
public get: () => Promise<ArrayBufferLike>,
|
|
@@ -86,6 +109,16 @@ export class BlobHandle
|
|
|
86
109
|
this.absolutePath = generateHandleContextPath(path, this.routeContext);
|
|
87
110
|
}
|
|
88
111
|
|
|
112
|
+
public readonly notifyShared = (): void => {
|
|
113
|
+
this._state = "shared";
|
|
114
|
+
this._events?.emit("payloadShared");
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
public readonly notifyFailed = (error: unknown): void => {
|
|
118
|
+
this._payloadShareError = error;
|
|
119
|
+
this._events?.emit("payloadShareFailed", error);
|
|
120
|
+
};
|
|
121
|
+
|
|
89
122
|
public attachGraph(): void {
|
|
90
123
|
if (!this.attached) {
|
|
91
124
|
this.attached = true;
|
|
@@ -93,6 +126,10 @@ export class BlobHandle
|
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
|
|
129
|
+
// eslint-disable-next-line jsdoc/require-description
|
|
130
|
+
/**
|
|
131
|
+
* @deprecated No replacement provided. Arbitrary handles may not serve as a bind source.
|
|
132
|
+
*/
|
|
96
133
|
public bind(handle: IFluidHandleInternal): void {
|
|
97
134
|
throw new Error("Cannot bind to blob handle");
|
|
98
135
|
}
|
|
@@ -104,7 +141,7 @@ export type IBlobManagerRuntime = Pick<
|
|
|
104
141
|
IContainerRuntime,
|
|
105
142
|
"attachState" | "connected" | "baseLogger" | "clientDetails" | "disposed"
|
|
106
143
|
> &
|
|
107
|
-
|
|
144
|
+
IEventProvider<IContainerRuntimeEvents>;
|
|
108
145
|
|
|
109
146
|
type ICreateBlobResponseWithTTL = ICreateBlobResponse &
|
|
110
147
|
Partial<Record<"minTTLInSeconds", number>>;
|
|
@@ -133,13 +170,14 @@ export interface IPendingBlobs {
|
|
|
133
170
|
};
|
|
134
171
|
}
|
|
135
172
|
|
|
136
|
-
export interface IBlobManagerEvents
|
|
137
|
-
|
|
173
|
+
export interface IBlobManagerEvents {
|
|
174
|
+
noPendingBlobs: () => void;
|
|
138
175
|
}
|
|
139
176
|
|
|
140
|
-
interface IBlobManagerInternalEvents
|
|
141
|
-
(
|
|
142
|
-
|
|
177
|
+
interface IBlobManagerInternalEvents {
|
|
178
|
+
uploadFailed: (localId: string, error: unknown) => void;
|
|
179
|
+
handleAttached: (pending: PendingBlob) => void;
|
|
180
|
+
processedBlobAttach: (localId: string, storageId: string) => void;
|
|
143
181
|
}
|
|
144
182
|
|
|
145
183
|
const stashedPendingBlobOverrides: Pick<
|
|
@@ -157,11 +195,11 @@ export const blobManagerBasePath = "_blobs" as const;
|
|
|
157
195
|
export class BlobManager {
|
|
158
196
|
private readonly mc: MonitoringContext;
|
|
159
197
|
|
|
160
|
-
private readonly publicEvents =
|
|
161
|
-
public get events():
|
|
198
|
+
private readonly publicEvents = createEmitter<IBlobManagerEvents>();
|
|
199
|
+
public get events(): Listenable<IBlobManagerEvents> {
|
|
162
200
|
return this.publicEvents;
|
|
163
201
|
}
|
|
164
|
-
private readonly internalEvents =
|
|
202
|
+
private readonly internalEvents = createEmitter<IBlobManagerInternalEvents>();
|
|
165
203
|
|
|
166
204
|
/**
|
|
167
205
|
* Map of local IDs to storage IDs. Contains identity entries (storageId → storageId) for storage IDs. All requested IDs should
|
|
@@ -201,6 +239,8 @@ export class BlobManager {
|
|
|
201
239
|
new Map();
|
|
202
240
|
public readonly stashedBlobsUploadP: Promise<(void | ICreateBlobResponse)[]>;
|
|
203
241
|
|
|
242
|
+
private readonly createBlobPayloadPending: boolean;
|
|
243
|
+
|
|
204
244
|
public constructor(props: {
|
|
205
245
|
readonly routeContext: IFluidHandleContext;
|
|
206
246
|
|
|
@@ -238,6 +278,7 @@ export class BlobManager {
|
|
|
238
278
|
runtime,
|
|
239
279
|
stashedBlobs,
|
|
240
280
|
localBlobIdGenerator,
|
|
281
|
+
createBlobPayloadPending,
|
|
241
282
|
} = props;
|
|
242
283
|
this.routeContext = routeContext;
|
|
243
284
|
this.storage = storage;
|
|
@@ -245,6 +286,7 @@ export class BlobManager {
|
|
|
245
286
|
this.isBlobDeleted = isBlobDeleted;
|
|
246
287
|
this.runtime = runtime;
|
|
247
288
|
this.localBlobIdGenerator = localBlobIdGenerator ?? uuid;
|
|
289
|
+
this.createBlobPayloadPending = createBlobPayloadPending;
|
|
248
290
|
|
|
249
291
|
this.mc = createChildMonitoringContext({
|
|
250
292
|
logger: this.runtime.baseLogger,
|
|
@@ -362,6 +404,12 @@ export class BlobManager {
|
|
|
362
404
|
return this.redirectTable.get(blobId) !== undefined;
|
|
363
405
|
}
|
|
364
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Retrieve the blob with the given local blob id.
|
|
409
|
+
* @param blobId - The local blob id. Likely coming from a handle.
|
|
410
|
+
* @param payloadPending - Whether we suspect the payload may be pending and not available yet.
|
|
411
|
+
* @returns A promise which resolves to the blob contents
|
|
412
|
+
*/
|
|
365
413
|
public async getBlob(blobId: string, payloadPending: boolean): Promise<ArrayBufferLike> {
|
|
366
414
|
// Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
|
|
367
415
|
// an error, failing the call.
|
|
@@ -386,7 +434,9 @@ export class BlobManager {
|
|
|
386
434
|
} else {
|
|
387
435
|
const attachedStorageId = this.redirectTable.get(blobId);
|
|
388
436
|
if (!payloadPending) {
|
|
389
|
-
|
|
437
|
+
// Only blob handles explicitly marked with pending payload are permitted to exist without
|
|
438
|
+
// yet knowing their storage id. Otherwise they must already be associated with a storage id.
|
|
439
|
+
assert(attachedStorageId !== undefined, 0x11f /* "requesting unknown blobs" */);
|
|
390
440
|
}
|
|
391
441
|
// If we didn't find it in the redirectTable, assume the attach op is coming eventually and wait.
|
|
392
442
|
// We do this even if the local client doesn't have the blob payloadPending flag enabled, in case a
|
|
@@ -428,11 +478,11 @@ export class BlobManager {
|
|
|
428
478
|
0x384 /* requesting handle for unknown blob */,
|
|
429
479
|
);
|
|
430
480
|
const pending = this.pendingBlobs.get(localId);
|
|
431
|
-
// Create a callback function for once the
|
|
481
|
+
// Create a callback function for once the handle has been attached
|
|
432
482
|
const callback = pending
|
|
433
483
|
? () => {
|
|
434
484
|
pending.attached = true;
|
|
435
|
-
// Notify listeners (e.g. serialization process) that
|
|
485
|
+
// Notify listeners (e.g. serialization process) that handle has been attached
|
|
436
486
|
this.internalEvents.emit("handleAttached", pending);
|
|
437
487
|
this.deletePendingBlobMaybe(localId);
|
|
438
488
|
}
|
|
@@ -448,7 +498,7 @@ export class BlobManager {
|
|
|
448
498
|
|
|
449
499
|
private async createBlobDetached(
|
|
450
500
|
blob: ArrayBufferLike,
|
|
451
|
-
): Promise<
|
|
501
|
+
): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>> {
|
|
452
502
|
// Blobs created while the container is detached are stored in IDetachedBlobStorage.
|
|
453
503
|
// The 'IDocumentStorageService.createBlob()' call below will respond with a localId.
|
|
454
504
|
const response = await this.storage.createBlob(blob);
|
|
@@ -459,7 +509,7 @@ export class BlobManager {
|
|
|
459
509
|
public async createBlob(
|
|
460
510
|
blob: ArrayBufferLike,
|
|
461
511
|
signal?: AbortSignal,
|
|
462
|
-
): Promise<
|
|
512
|
+
): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>> {
|
|
463
513
|
if (this.runtime.attachState === AttachState.Detached) {
|
|
464
514
|
return this.createBlobDetached(blob);
|
|
465
515
|
}
|
|
@@ -473,6 +523,15 @@ export class BlobManager {
|
|
|
473
523
|
0x385 /* For clarity and paranoid defense against adding future attachment states */,
|
|
474
524
|
);
|
|
475
525
|
|
|
526
|
+
return this.createBlobPayloadPending
|
|
527
|
+
? this.createBlobWithPayloadPending(blob)
|
|
528
|
+
: this.createBlobLegacy(blob, signal);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private async createBlobLegacy(
|
|
532
|
+
blob: ArrayBufferLike,
|
|
533
|
+
signal?: AbortSignal,
|
|
534
|
+
): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>> {
|
|
476
535
|
if (signal?.aborted) {
|
|
477
536
|
throw this.createAbortError();
|
|
478
537
|
}
|
|
@@ -503,6 +562,48 @@ export class BlobManager {
|
|
|
503
562
|
});
|
|
504
563
|
}
|
|
505
564
|
|
|
565
|
+
private createBlobWithPayloadPending(
|
|
566
|
+
blob: ArrayBufferLike,
|
|
567
|
+
): IFluidHandleInternalPayloadPending<ArrayBufferLike> {
|
|
568
|
+
const localId = this.localBlobIdGenerator();
|
|
569
|
+
|
|
570
|
+
const blobHandle = new BlobHandle(
|
|
571
|
+
getGCNodePathFromBlobId(localId),
|
|
572
|
+
this.routeContext,
|
|
573
|
+
async () => blob,
|
|
574
|
+
true, // payloadPending
|
|
575
|
+
() => {
|
|
576
|
+
const pendingEntry: PendingBlob = {
|
|
577
|
+
blob,
|
|
578
|
+
handleP: new Deferred(),
|
|
579
|
+
uploadP: this.uploadBlob(localId, blob),
|
|
580
|
+
attached: true,
|
|
581
|
+
acked: false,
|
|
582
|
+
opsent: false,
|
|
583
|
+
};
|
|
584
|
+
this.pendingBlobs.set(localId, pendingEntry);
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const onProcessedBlobAttach = (_localId: string, _storageId: string): void => {
|
|
589
|
+
if (_localId === localId) {
|
|
590
|
+
this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
|
|
591
|
+
blobHandle.notifyShared();
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
|
|
595
|
+
|
|
596
|
+
const onUploadFailed = (_localId: string, error: unknown): void => {
|
|
597
|
+
if (_localId === localId) {
|
|
598
|
+
this.internalEvents.off("uploadFailed", onUploadFailed);
|
|
599
|
+
blobHandle.notifyFailed(error);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
this.internalEvents.on("uploadFailed", onUploadFailed);
|
|
603
|
+
|
|
604
|
+
return blobHandle;
|
|
605
|
+
}
|
|
606
|
+
|
|
506
607
|
private async uploadBlob(
|
|
507
608
|
localId: string,
|
|
508
609
|
blob: ArrayBufferLike,
|
|
@@ -546,6 +647,7 @@ export class BlobManager {
|
|
|
546
647
|
// the promise but not throw any error outside.
|
|
547
648
|
this.pendingBlobs.get(localId)?.handleP.reject(error);
|
|
548
649
|
this.deletePendingBlob(localId);
|
|
650
|
+
this.internalEvents.emit("uploadFailed", localId, error);
|
|
549
651
|
},
|
|
550
652
|
);
|
|
551
653
|
}
|
|
@@ -619,7 +721,9 @@ export class BlobManager {
|
|
|
619
721
|
// an existing blob, we don't have to wait for the op to be ack'd since this step has already
|
|
620
722
|
// happened before and so, the server won't delete it.
|
|
621
723
|
this.setRedirection(localId, response.id);
|
|
622
|
-
|
|
724
|
+
const blobHandle = this.getBlobHandle(localId);
|
|
725
|
+
blobHandle.notifyShared();
|
|
726
|
+
entry.handleP.resolve(blobHandle);
|
|
623
727
|
this.deletePendingBlobMaybe(localId);
|
|
624
728
|
} else {
|
|
625
729
|
// If there is already an op for this storage ID, append the local ID to the list. Once any op for
|
|
@@ -682,8 +786,8 @@ export class BlobManager {
|
|
|
682
786
|
// set identity (id -> id) entry
|
|
683
787
|
this.setRedirection(blobId, blobId);
|
|
684
788
|
|
|
789
|
+
assert(localId !== undefined, 0x50e /* local ID not present in blob attach message */);
|
|
685
790
|
if (local) {
|
|
686
|
-
assert(localId !== undefined, 0x50e /* local ID not present in blob attach message */);
|
|
687
791
|
const waitingBlobs = this.opsInFlight.get(blobId);
|
|
688
792
|
if (waitingBlobs !== undefined) {
|
|
689
793
|
// For each op corresponding to this storage ID that we are waiting for, resolve the pending blob.
|
|
@@ -697,7 +801,9 @@ export class BlobManager {
|
|
|
697
801
|
);
|
|
698
802
|
this.setRedirection(pendingLocalId, blobId);
|
|
699
803
|
entry.acked = true;
|
|
700
|
-
|
|
804
|
+
const blobHandle = this.getBlobHandle(pendingLocalId);
|
|
805
|
+
blobHandle.notifyShared();
|
|
806
|
+
entry.handleP.resolve(blobHandle);
|
|
701
807
|
this.deletePendingBlobMaybe(pendingLocalId);
|
|
702
808
|
}
|
|
703
809
|
this.opsInFlight.delete(blobId);
|
|
@@ -705,7 +811,9 @@ export class BlobManager {
|
|
|
705
811
|
const localEntry = this.pendingBlobs.get(localId);
|
|
706
812
|
if (localEntry) {
|
|
707
813
|
localEntry.acked = true;
|
|
708
|
-
|
|
814
|
+
const blobHandle = this.getBlobHandle(localId);
|
|
815
|
+
blobHandle.notifyShared();
|
|
816
|
+
localEntry.handleP.resolve(blobHandle);
|
|
709
817
|
this.deletePendingBlobMaybe(localId);
|
|
710
818
|
}
|
|
711
819
|
}
|
|
@@ -875,7 +983,7 @@ export class BlobManager {
|
|
|
875
983
|
const localBlobs = new Set<PendingBlob>();
|
|
876
984
|
// This while is used to stash blobs created while attaching and getting blobs
|
|
877
985
|
while (localBlobs.size < this.pendingBlobs.size) {
|
|
878
|
-
const
|
|
986
|
+
const attachHandlesP: Promise<void>[] = [];
|
|
879
987
|
for (const [localId, entry] of this.pendingBlobs) {
|
|
880
988
|
if (!localBlobs.has(entry)) {
|
|
881
989
|
localBlobs.add(entry);
|
|
@@ -890,8 +998,8 @@ export class BlobManager {
|
|
|
890
998
|
// original createBlob call) and let them attach the blob. This is a lie we told since the upload
|
|
891
999
|
// hasn't finished yet, but it's fine since we will retry on rehydration.
|
|
892
1000
|
entry.handleP.resolve(this.getBlobHandle(localId));
|
|
893
|
-
// Array of promises that will resolve when
|
|
894
|
-
|
|
1001
|
+
// Array of promises that will resolve when handles get attached.
|
|
1002
|
+
attachHandlesP.push(
|
|
895
1003
|
new Promise<void>((resolve, reject) => {
|
|
896
1004
|
stopBlobAttachingSignal?.addEventListener(
|
|
897
1005
|
"abort",
|
|
@@ -901,16 +1009,16 @@ export class BlobManager {
|
|
|
901
1009
|
},
|
|
902
1010
|
{ once: true },
|
|
903
1011
|
);
|
|
904
|
-
const
|
|
1012
|
+
const onHandleAttached = (attachedEntry: PendingBlob): void => {
|
|
905
1013
|
if (attachedEntry === entry) {
|
|
906
|
-
this.internalEvents.off("handleAttached",
|
|
1014
|
+
this.internalEvents.off("handleAttached", onHandleAttached);
|
|
907
1015
|
resolve();
|
|
908
1016
|
}
|
|
909
1017
|
};
|
|
910
1018
|
if (entry.attached) {
|
|
911
1019
|
resolve();
|
|
912
1020
|
} else {
|
|
913
|
-
this.internalEvents.on("handleAttached",
|
|
1021
|
+
this.internalEvents.on("handleAttached", onHandleAttached);
|
|
914
1022
|
}
|
|
915
1023
|
}),
|
|
916
1024
|
);
|
|
@@ -918,7 +1026,7 @@ export class BlobManager {
|
|
|
918
1026
|
}
|
|
919
1027
|
// Wait for all blobs to be attached. This is important, otherwise serialized container
|
|
920
1028
|
// could send the blobAttach op without any op that references the blob, making it useless.
|
|
921
|
-
await Promise.allSettled(
|
|
1029
|
+
await Promise.allSettled(attachHandlesP);
|
|
922
1030
|
}
|
|
923
1031
|
|
|
924
1032
|
for (const [localId, entry] of this.pendingBlobs) {
|
package/src/channelCollection.ts
CHANGED
|
@@ -120,11 +120,22 @@ interface FluidDataStoreMessage {
|
|
|
120
120
|
type: string;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* This version of the interface is private to this the package. it should never be exported under any tag.
|
|
125
|
+
* It is used to manage interactions within the container-runtime package. If something is needed
|
|
126
|
+
* cross package, it is likely it is also being used cross layer (ContainerRuntime * DataStoreRuntime).
|
|
127
|
+
* If that is the case, the change likely needs to be staged directly on IFluidParentContext. Changes
|
|
128
|
+
* being staged on IFluidParentContext can be added here as well, likely with optionality removed,
|
|
129
|
+
* to ease interactions within this package.
|
|
130
|
+
*/
|
|
131
|
+
export interface IFluidParentContextPrivate extends Omit<IFluidParentContext, "isReadOnly"> {
|
|
132
|
+
readonly isReadOnly: () => boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
123
135
|
/**
|
|
124
136
|
* Creates a shallow wrapper of {@link IFluidParentContext}. The wrapper can then have its methods overwritten as needed
|
|
125
137
|
*/
|
|
126
|
-
export function wrapContext(context:
|
|
127
|
-
const isReadOnly = context.isReadOnly;
|
|
138
|
+
export function wrapContext(context: IFluidParentContextPrivate): IFluidParentContextPrivate {
|
|
128
139
|
return {
|
|
129
140
|
get IFluidDataStoreRegistry() {
|
|
130
141
|
return context.IFluidDataStoreRegistry;
|
|
@@ -150,7 +161,7 @@ export function wrapContext(context: IFluidParentContext): IFluidParentContext {
|
|
|
150
161
|
get attachState() {
|
|
151
162
|
return context.attachState;
|
|
152
163
|
},
|
|
153
|
-
isReadOnly:
|
|
164
|
+
isReadOnly: () => context.isReadOnly(),
|
|
154
165
|
containerRuntime: context.containerRuntime,
|
|
155
166
|
scope: context.scope,
|
|
156
167
|
gcThrowOnTombstoneUsage: context.gcThrowOnTombstoneUsage,
|
|
@@ -201,8 +212,8 @@ export function wrapContext(context: IFluidParentContext): IFluidParentContext {
|
|
|
201
212
|
*/
|
|
202
213
|
function wrapContextForInnerChannel(
|
|
203
214
|
id: string,
|
|
204
|
-
parentContext:
|
|
205
|
-
):
|
|
215
|
+
parentContext: IFluidParentContextPrivate,
|
|
216
|
+
): IFluidParentContextPrivate {
|
|
206
217
|
const context = wrapContext(parentContext);
|
|
207
218
|
|
|
208
219
|
context.submitMessage = (type: string, content: unknown, localOpMetadata: unknown) => {
|
|
@@ -274,7 +285,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
274
285
|
|
|
275
286
|
constructor(
|
|
276
287
|
protected readonly baseSnapshot: ISnapshotTree | ISnapshot | undefined,
|
|
277
|
-
public readonly parentContext:
|
|
288
|
+
public readonly parentContext: IFluidParentContextPrivate,
|
|
278
289
|
baseLogger: ITelemetryBaseLogger,
|
|
279
290
|
private readonly gcNodeUpdated: (props: IGCNodeUpdatedProps) => void,
|
|
280
291
|
private readonly isDataStoreDeleted: (nodePath: string) => boolean,
|
|
@@ -372,7 +383,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
372
383
|
*/
|
|
373
384
|
private shouldSendAttachLog = true;
|
|
374
385
|
|
|
375
|
-
protected wrapContextForInnerChannel(id: string):
|
|
386
|
+
protected wrapContextForInnerChannel(id: string): IFluidParentContextPrivate {
|
|
376
387
|
return wrapContextForInnerChannel(id, this.parentContext);
|
|
377
388
|
}
|
|
378
389
|
|
|
@@ -692,7 +703,12 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
692
703
|
}
|
|
693
704
|
public readonly dispose = (): void => this.disposeOnce.value;
|
|
694
705
|
|
|
695
|
-
public reSubmit(
|
|
706
|
+
public reSubmit(
|
|
707
|
+
type: string,
|
|
708
|
+
content: unknown,
|
|
709
|
+
localOpMetadata: unknown,
|
|
710
|
+
squash: boolean,
|
|
711
|
+
): void {
|
|
696
712
|
switch (type) {
|
|
697
713
|
case ContainerMessageType.Attach:
|
|
698
714
|
case ContainerMessageType.Alias: {
|
|
@@ -700,7 +716,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
700
716
|
return;
|
|
701
717
|
}
|
|
702
718
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
703
|
-
return this.reSubmitChannelOp(type, content, localOpMetadata);
|
|
719
|
+
return this.reSubmitChannelOp(type, content, localOpMetadata, squash);
|
|
704
720
|
}
|
|
705
721
|
default: {
|
|
706
722
|
assert(false, 0x907 /* unknown op type */);
|
|
@@ -708,7 +724,12 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
708
724
|
}
|
|
709
725
|
}
|
|
710
726
|
|
|
711
|
-
protected reSubmitChannelOp(
|
|
727
|
+
protected reSubmitChannelOp(
|
|
728
|
+
type: string,
|
|
729
|
+
content: unknown,
|
|
730
|
+
localOpMetadata: unknown,
|
|
731
|
+
squash: boolean,
|
|
732
|
+
): void {
|
|
712
733
|
const envelope = content as IEnvelope;
|
|
713
734
|
const context = this.contexts.get(envelope.address);
|
|
714
735
|
// If the data store has been deleted, log an error and throw an error. If there are local changes for a
|
|
@@ -723,7 +744,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
723
744
|
}
|
|
724
745
|
assert(!!context, 0x160 /* "There should be a store context for the op" */);
|
|
725
746
|
const innerContents = envelope.contents as FluidDataStoreMessage;
|
|
726
|
-
context.reSubmit(innerContents.type, innerContents.content, localOpMetadata);
|
|
747
|
+
context.reSubmit(innerContents.type, innerContents.content, localOpMetadata, squash);
|
|
727
748
|
}
|
|
728
749
|
|
|
729
750
|
public rollback(type: string, content: unknown, localOpMetadata: unknown): void {
|
|
@@ -1124,7 +1145,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
1124
1145
|
public notifyReadOnlyState(readonly: boolean): void {
|
|
1125
1146
|
for (const [fluidDataStoreId, context] of this.contexts) {
|
|
1126
1147
|
try {
|
|
1127
|
-
context.notifyReadOnlyState(
|
|
1148
|
+
context.notifyReadOnlyState();
|
|
1128
1149
|
} catch (error) {
|
|
1129
1150
|
this.mc.logger.sendErrorEvent(
|
|
1130
1151
|
{
|
|
@@ -1133,7 +1154,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
1133
1154
|
fluidDataStoreId,
|
|
1134
1155
|
}),
|
|
1135
1156
|
details: {
|
|
1136
|
-
runtimeReadonly: this.parentContext.isReadOnly
|
|
1157
|
+
runtimeReadonly: this.parentContext.isReadOnly(),
|
|
1137
1158
|
readonly,
|
|
1138
1159
|
},
|
|
1139
1160
|
},
|
|
@@ -1143,6 +1164,32 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
1143
1164
|
}
|
|
1144
1165
|
}
|
|
1145
1166
|
|
|
1167
|
+
/**
|
|
1168
|
+
* Notifies all data store contexts about the current staging mode state.
|
|
1169
|
+
*
|
|
1170
|
+
* @param staging - A boolean indicating whether the container is in staging mode.
|
|
1171
|
+
*/
|
|
1172
|
+
public notifyStagingMode(staging: boolean): void {
|
|
1173
|
+
for (const [fluidDataStoreId, context] of this.contexts) {
|
|
1174
|
+
try {
|
|
1175
|
+
context.notifyStagingMode(staging);
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
this.mc.logger.sendErrorEvent(
|
|
1178
|
+
{
|
|
1179
|
+
eventName: "notifyStagingModeError",
|
|
1180
|
+
...tagCodeArtifacts({
|
|
1181
|
+
fluidDataStoreId,
|
|
1182
|
+
}),
|
|
1183
|
+
details: {
|
|
1184
|
+
staging,
|
|
1185
|
+
},
|
|
1186
|
+
},
|
|
1187
|
+
error,
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1146
1193
|
public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
|
|
1147
1194
|
for (const [, context] of this.contexts) {
|
|
1148
1195
|
// Fire only for bounded stores.
|
|
@@ -1622,9 +1669,7 @@ export function detectOutboundReferences(
|
|
|
1622
1669
|
/**
|
|
1623
1670
|
* @internal
|
|
1624
1671
|
*/
|
|
1625
|
-
export class ChannelCollectionFactory
|
|
1626
|
-
implements IFluidDataStoreFactory
|
|
1627
|
-
{
|
|
1672
|
+
export class ChannelCollectionFactory implements IFluidDataStoreFactory {
|
|
1628
1673
|
public readonly type = "ChannelCollectionChannel";
|
|
1629
1674
|
|
|
1630
1675
|
public IFluidDataStoreRegistry: IFluidDataStoreRegistry;
|
|
@@ -1635,12 +1680,11 @@ export class ChannelCollectionFactory<T extends ChannelCollection = ChannelColle
|
|
|
1635
1680
|
private readonly provideEntryPoint: (
|
|
1636
1681
|
runtime: IFluidDataStoreChannel,
|
|
1637
1682
|
) => Promise<FluidObject>,
|
|
1638
|
-
private readonly ctor: (...args: ConstructorParameters<typeof ChannelCollection>) => T,
|
|
1639
1683
|
) {
|
|
1640
1684
|
this.IFluidDataStoreRegistry = new FluidDataStoreRegistry(registryEntries);
|
|
1641
1685
|
}
|
|
1642
1686
|
|
|
1643
|
-
public get IFluidDataStoreFactory(): ChannelCollectionFactory
|
|
1687
|
+
public get IFluidDataStoreFactory(): ChannelCollectionFactory {
|
|
1644
1688
|
return this;
|
|
1645
1689
|
}
|
|
1646
1690
|
|
|
@@ -1648,7 +1692,21 @@ export class ChannelCollectionFactory<T extends ChannelCollection = ChannelColle
|
|
|
1648
1692
|
context: IFluidDataStoreContext,
|
|
1649
1693
|
_existing: boolean,
|
|
1650
1694
|
): Promise<IFluidDataStoreChannel> {
|
|
1651
|
-
|
|
1695
|
+
// when work is done to complete nested datastores
|
|
1696
|
+
// this class should move to a new package
|
|
1697
|
+
// and IFluidDataStoreContext will need to support
|
|
1698
|
+
// all the cross layer needs of the channel context,
|
|
1699
|
+
// and the below assert should be removed.
|
|
1700
|
+
//
|
|
1701
|
+
// for now this will continue to work if both
|
|
1702
|
+
// this factory and the container runtime are
|
|
1703
|
+
// from the same package.
|
|
1704
|
+
assert(
|
|
1705
|
+
context instanceof FluidDataStoreContext,
|
|
1706
|
+
0xb8f /* we don't support the layer boundary here today */,
|
|
1707
|
+
);
|
|
1708
|
+
|
|
1709
|
+
const runtime = new ChannelCollection(
|
|
1652
1710
|
context.baseSnapshot,
|
|
1653
1711
|
context, // parentContext
|
|
1654
1712
|
context.baseLogger,
|