@fluidframework/container-loader 2.0.0-internal.5.1.1 → 2.0.0-internal.5.2.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 +16 -0
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +3 -0
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +10 -8
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +20 -27
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +164 -104
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +22 -58
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +27 -200
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
- package/dist/noopHeuristic.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +1 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +0 -18
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +3 -0
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +10 -8
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +20 -27
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +166 -106
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +22 -58
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +27 -200
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/noopHeuristic.d.ts +23 -0
- package/lib/noopHeuristic.d.ts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.js} +30 -42
- package/lib/noopHeuristic.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +1 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +0 -18
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/package.json +11 -11
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +2 -1
- package/src/connectionStateHandler.ts +14 -9
- package/src/container.ts +247 -126
- package/src/containerContext.ts +32 -318
- package/src/containerStorageAdapter.ts +1 -1
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +0 -39
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/retriableDocumentStorageService.ts +2 -1
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js.map +0 -1
- package/lib/collabWindowTracker.d.ts +0 -19
- package/lib/collabWindowTracker.d.ts.map +0 -1
- package/lib/collabWindowTracker.js.map +0 -1
package/src/container.ts
CHANGED
|
@@ -7,8 +7,17 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
IEvent,
|
|
12
|
+
ITelemetryProperties,
|
|
13
|
+
TelemetryEventCategory,
|
|
14
|
+
} from "@fluidframework/common-definitions";
|
|
15
|
+
import {
|
|
16
|
+
TypedEventEmitter,
|
|
17
|
+
assert,
|
|
18
|
+
performance,
|
|
19
|
+
unreachableCase,
|
|
20
|
+
} from "@fluidframework/common-utils";
|
|
12
21
|
import { IRequest, IResponse, IFluidRouter, FluidObject } from "@fluidframework/core-interfaces";
|
|
13
22
|
import {
|
|
14
23
|
IAudience,
|
|
@@ -27,6 +36,11 @@ import {
|
|
|
27
36
|
IBatchMessage,
|
|
28
37
|
ICodeDetailsLoader,
|
|
29
38
|
IHostLoader,
|
|
39
|
+
IFluidModuleWithDetails,
|
|
40
|
+
IProvideRuntimeFactory,
|
|
41
|
+
IProvideFluidCodeDetailsComparer,
|
|
42
|
+
IFluidCodeDetailsComparer,
|
|
43
|
+
IRuntime,
|
|
30
44
|
} from "@fluidframework/container-definitions";
|
|
31
45
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
32
46
|
import {
|
|
@@ -44,11 +58,12 @@ import {
|
|
|
44
58
|
combineAppAndProtocolSummary,
|
|
45
59
|
runWithRetry,
|
|
46
60
|
isCombinedAppAndProtocolSummary,
|
|
61
|
+
MessageType2,
|
|
62
|
+
canBeCoalescedByService,
|
|
47
63
|
} from "@fluidframework/driver-utils";
|
|
48
64
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
49
65
|
import {
|
|
50
66
|
IClient,
|
|
51
|
-
IClientConfiguration,
|
|
52
67
|
IClientDetails,
|
|
53
68
|
ICommittedProposal,
|
|
54
69
|
IDocumentAttributes,
|
|
@@ -99,7 +114,7 @@ import {
|
|
|
99
114
|
getCodeDetailsFromQuorumValues,
|
|
100
115
|
QuorumProxy,
|
|
101
116
|
} from "./quorum";
|
|
102
|
-
import {
|
|
117
|
+
import { NoopHeuristic } from "./noopHeuristic";
|
|
103
118
|
import { ConnectionManager } from "./connectionManager";
|
|
104
119
|
import { ConnectionState } from "./connectionState";
|
|
105
120
|
import {
|
|
@@ -114,6 +129,8 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
114
129
|
const dirtyContainerEvent = "dirty";
|
|
115
130
|
const savedContainerEvent = "saved";
|
|
116
131
|
|
|
132
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
133
|
+
|
|
117
134
|
/**
|
|
118
135
|
* @internal
|
|
119
136
|
*/
|
|
@@ -336,12 +353,15 @@ export interface IPendingContainerState {
|
|
|
336
353
|
|
|
337
354
|
const summarizerClientType = "summarizer";
|
|
338
355
|
|
|
356
|
+
interface IContainerLifecycleEvents extends IEvent {
|
|
357
|
+
(event: "runtimeInstantiated", listener: () => void): void;
|
|
358
|
+
(event: "disposed", listener: () => void): void;
|
|
359
|
+
}
|
|
360
|
+
|
|
339
361
|
export class Container
|
|
340
362
|
extends EventEmitterWithErrorHandling<IContainerEvents>
|
|
341
363
|
implements IContainer, IContainerExperimental
|
|
342
364
|
{
|
|
343
|
-
public static version = "^0.1.0";
|
|
344
|
-
|
|
345
365
|
/**
|
|
346
366
|
* Load an existing container.
|
|
347
367
|
* @internal
|
|
@@ -354,6 +374,10 @@ export class Container
|
|
|
354
374
|
|
|
355
375
|
const container = new Container(createProps, loadProps);
|
|
356
376
|
|
|
377
|
+
const disableRecordHeapSize = container.mc.config.getBoolean(
|
|
378
|
+
"Fluid.Loader.DisableRecordHeapSize",
|
|
379
|
+
);
|
|
380
|
+
|
|
357
381
|
return PerformanceEvent.timedExecAsync(
|
|
358
382
|
container.mc.logger,
|
|
359
383
|
{ eventName: "Load" },
|
|
@@ -395,6 +419,7 @@ export class Container
|
|
|
395
419
|
);
|
|
396
420
|
}),
|
|
397
421
|
{ start: true, end: true, cancel: "generic" },
|
|
422
|
+
disableRecordHeapSize !== true /* recordHeapSize */,
|
|
398
423
|
);
|
|
399
424
|
}
|
|
400
425
|
|
|
@@ -447,9 +472,9 @@ export class Container
|
|
|
447
472
|
private readonly urlResolver: IUrlResolver;
|
|
448
473
|
private readonly serviceFactory: IDocumentServiceFactory;
|
|
449
474
|
private readonly codeLoader: ICodeDetailsLoader;
|
|
450
|
-
|
|
475
|
+
private readonly options: ILoaderOptions;
|
|
451
476
|
private readonly scope: FluidObject;
|
|
452
|
-
|
|
477
|
+
private readonly subLogger: TelemetryLogger;
|
|
453
478
|
private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
|
|
454
479
|
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
|
|
455
480
|
|
|
@@ -509,19 +534,16 @@ export class Container
|
|
|
509
534
|
private _attachState = AttachState.Detached;
|
|
510
535
|
|
|
511
536
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
512
|
-
public get storage(): IDocumentStorageService {
|
|
513
|
-
return this.storageAdapter;
|
|
514
|
-
}
|
|
515
537
|
|
|
516
538
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
517
539
|
private service: IDocumentService | undefined;
|
|
518
540
|
|
|
519
|
-
private
|
|
520
|
-
private get
|
|
521
|
-
if (this.
|
|
522
|
-
throw new
|
|
541
|
+
private _runtime: IRuntime | undefined;
|
|
542
|
+
private get runtime() {
|
|
543
|
+
if (this._runtime === undefined) {
|
|
544
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
523
545
|
}
|
|
524
|
-
return this.
|
|
546
|
+
return this._runtime;
|
|
525
547
|
}
|
|
526
548
|
private _protocolHandler: IProtocolHandler | undefined;
|
|
527
549
|
private get protocolHandler() {
|
|
@@ -546,10 +568,11 @@ export class Container
|
|
|
546
568
|
private lastVisible: number | undefined;
|
|
547
569
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
548
570
|
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
571
|
+
private readonly clientsWhoShouldHaveLeft = new Set<string>();
|
|
549
572
|
|
|
550
573
|
private setAutoReconnectTime = performance.now();
|
|
551
574
|
|
|
552
|
-
private
|
|
575
|
+
private noopHeuristic: NoopHeuristic | undefined;
|
|
553
576
|
|
|
554
577
|
private get connectionMode() {
|
|
555
578
|
return this._deltaManager.connectionManager.connectionMode;
|
|
@@ -574,18 +597,10 @@ export class Container
|
|
|
574
597
|
return this.service?.resolvedUrl;
|
|
575
598
|
}
|
|
576
599
|
|
|
577
|
-
public get loadedFromVersion(): IVersion | undefined {
|
|
578
|
-
return this._loadedFromVersion;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
600
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
582
601
|
return this._deltaManager.readOnlyInfo;
|
|
583
602
|
}
|
|
584
603
|
|
|
585
|
-
public get closeSignal(): AbortSignal {
|
|
586
|
-
return this._deltaManager.closeAbortController.signal;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
604
|
/**
|
|
590
605
|
* Tracks host requiring read-only mode.
|
|
591
606
|
*/
|
|
@@ -601,18 +616,10 @@ export class Container
|
|
|
601
616
|
return this.connectionStateHandler.connectionState;
|
|
602
617
|
}
|
|
603
618
|
|
|
604
|
-
|
|
619
|
+
private get connected(): boolean {
|
|
605
620
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
606
621
|
}
|
|
607
622
|
|
|
608
|
-
/**
|
|
609
|
-
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
610
|
-
* configuration details returned as part of the initial connection.
|
|
611
|
-
*/
|
|
612
|
-
public get serviceConfiguration(): IClientConfiguration | undefined {
|
|
613
|
-
return this._deltaManager.serviceConfiguration;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
623
|
private _clientId: string | undefined;
|
|
617
624
|
|
|
618
625
|
/**
|
|
@@ -623,24 +630,12 @@ export class Container
|
|
|
623
630
|
return this._clientId;
|
|
624
631
|
}
|
|
625
632
|
|
|
626
|
-
/**
|
|
627
|
-
* The server provided claims of the client.
|
|
628
|
-
* Set once this.connected is true, otherwise undefined
|
|
629
|
-
*/
|
|
630
|
-
public get scopes(): string[] | undefined {
|
|
631
|
-
return this._deltaManager.connectionManager.scopes;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
public get clientDetails(): IClientDetails {
|
|
635
|
-
return this._deltaManager.clientDetails;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
633
|
private get offlineLoadEnabled(): boolean {
|
|
639
634
|
const enabled =
|
|
640
635
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
641
636
|
this.options?.enableOfflineLoad === true;
|
|
642
637
|
// summarizer will not have any pending state we want to save
|
|
643
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
638
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
644
639
|
}
|
|
645
640
|
|
|
646
641
|
/**
|
|
@@ -651,15 +646,18 @@ export class Container
|
|
|
651
646
|
return this.getCodeDetailsFromQuorum();
|
|
652
647
|
}
|
|
653
648
|
|
|
649
|
+
private _loadedCodeDetails: IFluidCodeDetails | undefined;
|
|
654
650
|
/**
|
|
655
651
|
* Get the code details that were used to load the container.
|
|
656
652
|
* @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
|
|
657
653
|
* loaded.
|
|
658
654
|
*/
|
|
659
655
|
public getLoadedCodeDetails(): IFluidCodeDetails | undefined {
|
|
660
|
-
return this.
|
|
656
|
+
return this._loadedCodeDetails;
|
|
661
657
|
}
|
|
662
658
|
|
|
659
|
+
private _loadedModule: IFluidModuleWithDetails | undefined;
|
|
660
|
+
|
|
663
661
|
/**
|
|
664
662
|
* Retrieves the audience associated with the document
|
|
665
663
|
*/
|
|
@@ -679,38 +677,33 @@ export class Container
|
|
|
679
677
|
/**
|
|
680
678
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
681
679
|
*/
|
|
682
|
-
public async getEntryPoint
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
686
|
-
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
687
|
-
throw new UsageError("The container is disposing or disposed");
|
|
680
|
+
public async getEntryPoint(): Promise<FluidObject | undefined> {
|
|
681
|
+
if (this._disposed) {
|
|
682
|
+
throw new UsageError("The context is already disposed");
|
|
688
683
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const contextChangedHandler = () => {
|
|
692
|
-
resolve();
|
|
693
|
-
this.off("disposed", disposedHandler);
|
|
694
|
-
};
|
|
695
|
-
const disposedHandler = (error) => {
|
|
696
|
-
reject(error ?? "The Container is disposed");
|
|
697
|
-
this.off("contextChanged", contextChangedHandler);
|
|
698
|
-
};
|
|
699
|
-
this.once("contextChanged", contextChangedHandler);
|
|
700
|
-
this.once("disposed", disposedHandler);
|
|
701
|
-
});
|
|
702
|
-
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
703
|
-
// should have set this._context; making sure.
|
|
704
|
-
assert(
|
|
705
|
-
this._context !== undefined,
|
|
706
|
-
0x5a2 /* Context still not defined after contextChanged event */,
|
|
707
|
-
);
|
|
684
|
+
if (this._runtime !== undefined) {
|
|
685
|
+
return this._runtime.getEntryPoint?.();
|
|
708
686
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
687
|
+
return new Promise<FluidObject | undefined>((resolve, reject) => {
|
|
688
|
+
const runtimeInstantiatedHandler = () => {
|
|
689
|
+
assert(
|
|
690
|
+
this._runtime !== undefined,
|
|
691
|
+
0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
|
|
692
|
+
);
|
|
693
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
694
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
695
|
+
};
|
|
696
|
+
const disposedHandler = () => {
|
|
697
|
+
reject(new Error("ContainerContext was disposed"));
|
|
698
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
699
|
+
};
|
|
700
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
701
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
702
|
+
});
|
|
712
703
|
}
|
|
713
704
|
|
|
705
|
+
private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
|
|
706
|
+
|
|
714
707
|
/**
|
|
715
708
|
* @internal
|
|
716
709
|
*/
|
|
@@ -795,8 +788,8 @@ export class Container
|
|
|
795
788
|
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
796
789
|
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
797
790
|
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
798
|
-
containerLoadedFromVersionId: () => this.
|
|
799
|
-
containerLoadedFromVersionDate: () => this.
|
|
791
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
792
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
800
793
|
// message information to associate errors with the specific execution state
|
|
801
794
|
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
802
795
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
@@ -866,6 +859,9 @@ export class Container
|
|
|
866
859
|
this.connect();
|
|
867
860
|
}
|
|
868
861
|
},
|
|
862
|
+
clientShouldHaveLeft: (clientId: string) => {
|
|
863
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
864
|
+
},
|
|
869
865
|
},
|
|
870
866
|
this.deltaManager,
|
|
871
867
|
pendingLocalState?.clientId,
|
|
@@ -1005,7 +1001,8 @@ export class Container
|
|
|
1005
1001
|
this.mc.logger.sendTelemetryEvent(
|
|
1006
1002
|
{
|
|
1007
1003
|
eventName: "ContainerDispose",
|
|
1008
|
-
|
|
1004
|
+
// Only log error if container isn't closed
|
|
1005
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
1009
1006
|
},
|
|
1010
1007
|
error,
|
|
1011
1008
|
);
|
|
@@ -1019,7 +1016,8 @@ export class Container
|
|
|
1019
1016
|
|
|
1020
1017
|
this.connectionStateHandler.dispose();
|
|
1021
1018
|
|
|
1022
|
-
|
|
1019
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
1020
|
+
this._runtime?.dispose(maybeError);
|
|
1023
1021
|
|
|
1024
1022
|
this.storageAdapter.dispose();
|
|
1025
1023
|
|
|
@@ -1042,6 +1040,7 @@ export class Container
|
|
|
1042
1040
|
}
|
|
1043
1041
|
} finally {
|
|
1044
1042
|
this._lifecycleState = "disposed";
|
|
1043
|
+
this._lifecycleEvents.emit("disposed");
|
|
1045
1044
|
}
|
|
1046
1045
|
}
|
|
1047
1046
|
|
|
@@ -1069,7 +1068,7 @@ export class Container
|
|
|
1069
1068
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1070
1069
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1071
1070
|
const pendingState: IPendingContainerState = {
|
|
1072
|
-
pendingRuntimeState: this.
|
|
1071
|
+
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
1073
1072
|
baseSnapshot: this.baseSnapshot,
|
|
1074
1073
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1075
1074
|
savedOps: this.savedOps,
|
|
@@ -1093,7 +1092,7 @@ export class Container
|
|
|
1093
1092
|
0x0d3 /* "Should only be called in detached container" */,
|
|
1094
1093
|
);
|
|
1095
1094
|
|
|
1096
|
-
const appSummary: ISummaryTree = this.
|
|
1095
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1097
1096
|
const protocolSummary = this.captureProtocolSummary();
|
|
1098
1097
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1099
1098
|
|
|
@@ -1139,7 +1138,7 @@ export class Container
|
|
|
1139
1138
|
if (!hasAttachmentBlobs) {
|
|
1140
1139
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
1141
1140
|
// semantics around what the attach means as far as async code goes.
|
|
1142
|
-
const appSummary: ISummaryTree = this.
|
|
1141
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1143
1142
|
const protocolSummary = this.captureProtocolSummary();
|
|
1144
1143
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1145
1144
|
|
|
@@ -1148,6 +1147,7 @@ export class Container
|
|
|
1148
1147
|
// starting to attach the container to storage.
|
|
1149
1148
|
// Also, this should only be fired in detached container.
|
|
1150
1149
|
this._attachState = AttachState.Attaching;
|
|
1150
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1151
1151
|
this.emit("attaching");
|
|
1152
1152
|
if (this.offlineLoadEnabled) {
|
|
1153
1153
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1176,7 +1176,7 @@ export class Container
|
|
|
1176
1176
|
"containerAttach",
|
|
1177
1177
|
this.mc.logger,
|
|
1178
1178
|
{
|
|
1179
|
-
cancel: this.
|
|
1179
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
1180
1180
|
}, // progress
|
|
1181
1181
|
);
|
|
1182
1182
|
}
|
|
@@ -1205,11 +1205,12 @@ export class Container
|
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
1207
|
// take summary and upload
|
|
1208
|
-
const appSummary: ISummaryTree = this.
|
|
1208
|
+
const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
|
|
1209
1209
|
const protocolSummary = this.captureProtocolSummary();
|
|
1210
1210
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1211
1211
|
|
|
1212
1212
|
this._attachState = AttachState.Attaching;
|
|
1213
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1213
1214
|
this.emit("attaching");
|
|
1214
1215
|
if (this.offlineLoadEnabled) {
|
|
1215
1216
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1226,6 +1227,7 @@ export class Container
|
|
|
1226
1227
|
}
|
|
1227
1228
|
|
|
1228
1229
|
this._attachState = AttachState.Attached;
|
|
1230
|
+
this.runtime.setAttachState(AttachState.Attached);
|
|
1229
1231
|
this.emit("attached");
|
|
1230
1232
|
|
|
1231
1233
|
if (!this.closed) {
|
|
@@ -1250,7 +1252,7 @@ export class Container
|
|
|
1250
1252
|
return PerformanceEvent.timedExecAsync(
|
|
1251
1253
|
this.mc.logger,
|
|
1252
1254
|
{ eventName: "Request" },
|
|
1253
|
-
async () => this.
|
|
1255
|
+
async () => this.runtime.request(path),
|
|
1254
1256
|
{ end: true, cancel: "error" },
|
|
1255
1257
|
);
|
|
1256
1258
|
}
|
|
@@ -1335,7 +1337,7 @@ export class Container
|
|
|
1335
1337
|
this.connectToDeltaStream(args);
|
|
1336
1338
|
}
|
|
1337
1339
|
|
|
1338
|
-
public async
|
|
1340
|
+
public readonly getAbsoluteUrl = async (relativeUrl: string): Promise<string | undefined> => {
|
|
1339
1341
|
if (this.resolvedUrl === undefined) {
|
|
1340
1342
|
return undefined;
|
|
1341
1343
|
}
|
|
@@ -1343,9 +1345,9 @@ export class Container
|
|
|
1343
1345
|
return this.urlResolver.getAbsoluteUrl(
|
|
1344
1346
|
this.resolvedUrl,
|
|
1345
1347
|
relativeUrl,
|
|
1346
|
-
getPackageName(this.
|
|
1348
|
+
getPackageName(this._loadedCodeDetails),
|
|
1347
1349
|
);
|
|
1348
|
-
}
|
|
1350
|
+
};
|
|
1349
1351
|
|
|
1350
1352
|
public async proposeCodeDetails(codeDetails: IFluidCodeDetails) {
|
|
1351
1353
|
if (!isFluidCodeDetails(codeDetails)) {
|
|
@@ -1376,7 +1378,7 @@ export class Container
|
|
|
1376
1378
|
this.deltaManager.inboundSignal.pause(),
|
|
1377
1379
|
]);
|
|
1378
1380
|
|
|
1379
|
-
if ((await this.
|
|
1381
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
1380
1382
|
this.deltaManager.inbound.resume();
|
|
1381
1383
|
this.deltaManager.inboundSignal.resume();
|
|
1382
1384
|
return;
|
|
@@ -1387,6 +1389,47 @@ export class Container
|
|
|
1387
1389
|
this.close(error);
|
|
1388
1390
|
}
|
|
1389
1391
|
|
|
1392
|
+
/**
|
|
1393
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
1394
|
+
*/
|
|
1395
|
+
private async satisfies(constraintCodeDetails: IFluidCodeDetails) {
|
|
1396
|
+
// If we have no module, it can't satisfy anything.
|
|
1397
|
+
if (this._loadedModule === undefined) {
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
const comparers: IFluidCodeDetailsComparer[] = [];
|
|
1402
|
+
|
|
1403
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
1404
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
1405
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
|
|
1409
|
+
this._loadedModule?.module.fluidExport;
|
|
1410
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
1411
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
1415
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
1416
|
+
// rather than potentially running with incompatible code.
|
|
1417
|
+
if (comparers.length === 0) {
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
for (const comparer of comparers) {
|
|
1422
|
+
const satisfies = await comparer.satisfies(
|
|
1423
|
+
this._loadedModule?.details,
|
|
1424
|
+
constraintCodeDetails,
|
|
1425
|
+
);
|
|
1426
|
+
if (satisfies === false) {
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return true;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1390
1433
|
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
1391
1434
|
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
1392
1435
|
return versions[0];
|
|
@@ -1464,7 +1507,10 @@ export class Container
|
|
|
1464
1507
|
if (this.offlineLoadEnabled) {
|
|
1465
1508
|
this.baseSnapshot = snapshot;
|
|
1466
1509
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
1467
|
-
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1510
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1511
|
+
snapshot,
|
|
1512
|
+
this.storageAdapter,
|
|
1513
|
+
);
|
|
1468
1514
|
}
|
|
1469
1515
|
}
|
|
1470
1516
|
|
|
@@ -1520,7 +1566,7 @@ export class Container
|
|
|
1520
1566
|
this.processRemoteMessage(message);
|
|
1521
1567
|
|
|
1522
1568
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
1523
|
-
await this.
|
|
1569
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
1524
1570
|
}
|
|
1525
1571
|
pendingLocalState.savedOps = [];
|
|
1526
1572
|
|
|
@@ -1872,7 +1918,7 @@ export class Container
|
|
|
1872
1918
|
});
|
|
1873
1919
|
|
|
1874
1920
|
deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
|
|
1875
|
-
this.
|
|
1921
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1876
1922
|
if (!this.closed) {
|
|
1877
1923
|
this.connectionStateHandler.receivedDisconnectEvent(reason, error);
|
|
1878
1924
|
}
|
|
@@ -2095,7 +2141,7 @@ export class Container
|
|
|
2095
2141
|
}
|
|
2096
2142
|
|
|
2097
2143
|
this.messageCountAfterDisconnection += 1;
|
|
2098
|
-
this.
|
|
2144
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
2099
2145
|
return this._deltaManager.submit(
|
|
2100
2146
|
type,
|
|
2101
2147
|
contents,
|
|
@@ -2112,39 +2158,67 @@ export class Container
|
|
|
2112
2158
|
}
|
|
2113
2159
|
const local = this.clientId === message.clientId;
|
|
2114
2160
|
|
|
2161
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
2162
|
+
// flagged should have left, or from a client that's not in the quorum but should be
|
|
2163
|
+
if (message.clientId != null) {
|
|
2164
|
+
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
2165
|
+
|
|
2166
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
2167
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
2168
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
2172
|
+
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
2173
|
+
// document we don't need to blow up aggressively.
|
|
2174
|
+
if (
|
|
2175
|
+
this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
2176
|
+
!canBeCoalescedByService(message)
|
|
2177
|
+
) {
|
|
2178
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
2179
|
+
throw new Error("Remote message's clientId already should have left");
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2115
2183
|
// Allow the protocol handler to process the message
|
|
2116
2184
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2117
2185
|
|
|
2118
2186
|
// Forward messages to the loaded runtime for processing
|
|
2119
|
-
this.
|
|
2187
|
+
this.runtime.process(message, local);
|
|
2120
2188
|
|
|
2121
2189
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
2122
2190
|
if (this.activeConnection()) {
|
|
2123
|
-
if (this.
|
|
2191
|
+
if (this.noopHeuristic === undefined) {
|
|
2192
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
2124
2193
|
// Note that config from first connection will be used for this container's lifetime.
|
|
2125
2194
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
2126
2195
|
// clients.
|
|
2127
2196
|
// All existing will continue to use settings they got earlier.
|
|
2128
2197
|
assert(
|
|
2129
|
-
|
|
2198
|
+
serviceConfiguration !== undefined,
|
|
2130
2199
|
0x2e4 /* "there should be service config for active connection" */,
|
|
2131
2200
|
);
|
|
2132
|
-
this.
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
this.activeConnection(),
|
|
2136
|
-
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */,
|
|
2137
|
-
);
|
|
2138
|
-
this.submitMessage(type);
|
|
2139
|
-
},
|
|
2140
|
-
this.serviceConfiguration.noopTimeFrequency,
|
|
2141
|
-
this.serviceConfiguration.noopCountFrequency,
|
|
2201
|
+
this.noopHeuristic = new NoopHeuristic(
|
|
2202
|
+
serviceConfiguration.noopTimeFrequency,
|
|
2203
|
+
serviceConfiguration.noopCountFrequency,
|
|
2142
2204
|
);
|
|
2205
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
2206
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
2207
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
2208
|
+
// running the microtask that the heuristic queued in response.
|
|
2209
|
+
assert(
|
|
2210
|
+
this.activeConnection(),
|
|
2211
|
+
0x241 /* "Trying to send noop without active connection" */,
|
|
2212
|
+
);
|
|
2213
|
+
this.submitMessage(MessageType.NoOp);
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
2217
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
2218
|
+
if (result.immediateNoOp === true) {
|
|
2219
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
2220
|
+
this.submitMessage(MessageType2.Accept as unknown as MessageType);
|
|
2143
2221
|
}
|
|
2144
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(
|
|
2145
|
-
message,
|
|
2146
|
-
result.immediateNoOp === true,
|
|
2147
|
-
);
|
|
2148
2222
|
}
|
|
2149
2223
|
|
|
2150
2224
|
this.emit("op", message);
|
|
@@ -2160,7 +2234,7 @@ export class Container
|
|
|
2160
2234
|
this.protocolHandler.processSignal(message);
|
|
2161
2235
|
} else {
|
|
2162
2236
|
const local = this.clientId === message.clientId;
|
|
2163
|
-
this.
|
|
2237
|
+
this.runtime.processSignal(message, local);
|
|
2164
2238
|
}
|
|
2165
2239
|
}
|
|
2166
2240
|
|
|
@@ -2202,23 +2276,49 @@ export class Container
|
|
|
2202
2276
|
private async instantiateContext(
|
|
2203
2277
|
existing: boolean,
|
|
2204
2278
|
codeDetails: IFluidCodeDetails,
|
|
2205
|
-
snapshot
|
|
2279
|
+
snapshot: ISnapshotTree | undefined,
|
|
2206
2280
|
pendingLocalState?: unknown,
|
|
2207
2281
|
) {
|
|
2208
|
-
assert(this.
|
|
2282
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
2209
2283
|
|
|
2210
2284
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
2211
2285
|
// are set. Global requests will still go directly to the loader
|
|
2212
2286
|
const maybeLoader: FluidObject<IHostLoader> = this.scope;
|
|
2213
2287
|
const loader = new RelativeLoader(this, maybeLoader.ILoader);
|
|
2214
|
-
|
|
2215
|
-
|
|
2288
|
+
|
|
2289
|
+
const loadCodeResult = await PerformanceEvent.timedExecAsync(
|
|
2290
|
+
this.subLogger,
|
|
2291
|
+
{ eventName: "CodeLoad" },
|
|
2292
|
+
async () => this.codeLoader.load(codeDetails),
|
|
2293
|
+
);
|
|
2294
|
+
|
|
2295
|
+
this._loadedModule = {
|
|
2296
|
+
module: loadCodeResult.module,
|
|
2297
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
2298
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
2299
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
2300
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
2301
|
+
};
|
|
2302
|
+
|
|
2303
|
+
const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
|
|
2304
|
+
this._loadedModule.module.fluidExport;
|
|
2305
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
2306
|
+
if (runtimeFactory === undefined) {
|
|
2307
|
+
throw new Error(packageNotFactoryError);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
const deltaManagerProxy = new DeltaManagerProxy(this._deltaManager);
|
|
2311
|
+
const quorumProxy = new QuorumProxy(this.protocolHandler.quorum);
|
|
2312
|
+
|
|
2313
|
+
const context = new ContainerContext(
|
|
2314
|
+
this.options,
|
|
2216
2315
|
this.scope,
|
|
2217
|
-
this.codeLoader,
|
|
2218
|
-
codeDetails,
|
|
2219
2316
|
snapshot,
|
|
2220
|
-
|
|
2221
|
-
|
|
2317
|
+
this._loadedFromVersion,
|
|
2318
|
+
deltaManagerProxy,
|
|
2319
|
+
this.storageAdapter,
|
|
2320
|
+
quorumProxy,
|
|
2321
|
+
this.protocolHandler.audience,
|
|
2222
2322
|
loader,
|
|
2223
2323
|
(type, contents, batch, metadata) =>
|
|
2224
2324
|
this.submitContainerMessage(type, contents, batch, metadata),
|
|
@@ -2229,22 +2329,43 @@ export class Container
|
|
|
2229
2329
|
(message) => this.submitSignal(message),
|
|
2230
2330
|
(error?: ICriticalContainerError) => this.dispose(error),
|
|
2231
2331
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2232
|
-
|
|
2233
|
-
|
|
2332
|
+
this.updateDirtyContainerState,
|
|
2333
|
+
this.getAbsoluteUrl,
|
|
2334
|
+
() => this.resolvedUrl?.id,
|
|
2335
|
+
() => this.clientId,
|
|
2336
|
+
() => deltaManagerProxy.serviceConfiguration,
|
|
2337
|
+
() => this.attachState,
|
|
2338
|
+
() => this.connected,
|
|
2339
|
+
this._deltaManager.clientDetails,
|
|
2234
2340
|
existing,
|
|
2341
|
+
this.subLogger,
|
|
2235
2342
|
pendingLocalState,
|
|
2236
2343
|
);
|
|
2344
|
+
this._lifecycleEvents.once("disposed", () => {
|
|
2345
|
+
context.dispose();
|
|
2346
|
+
quorumProxy.dispose();
|
|
2347
|
+
deltaManagerProxy.dispose();
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2351
|
+
this.subLogger,
|
|
2352
|
+
{ eventName: "InstantiateRuntime" },
|
|
2353
|
+
async () => runtimeFactory.instantiateRuntime(context, existing),
|
|
2354
|
+
);
|
|
2355
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
2356
|
+
|
|
2357
|
+
this._loadedCodeDetails = codeDetails;
|
|
2237
2358
|
|
|
2238
2359
|
this.emit("contextChanged", codeDetails);
|
|
2239
2360
|
}
|
|
2240
2361
|
|
|
2241
|
-
private updateDirtyContainerState(dirty: boolean) {
|
|
2362
|
+
private readonly updateDirtyContainerState = (dirty: boolean) => {
|
|
2242
2363
|
if (this._dirtyContainer === dirty) {
|
|
2243
2364
|
return;
|
|
2244
2365
|
}
|
|
2245
2366
|
this._dirtyContainer = dirty;
|
|
2246
2367
|
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
2247
|
-
}
|
|
2368
|
+
};
|
|
2248
2369
|
|
|
2249
2370
|
/**
|
|
2250
2371
|
* Set the connected state of the ContainerContext
|
|
@@ -2253,14 +2374,14 @@ export class Container
|
|
|
2253
2374
|
* @param readonly - Is the container in readonly mode?
|
|
2254
2375
|
*/
|
|
2255
2376
|
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
2256
|
-
if (this.
|
|
2377
|
+
if (this._runtime?.disposed === false) {
|
|
2257
2378
|
/**
|
|
2258
2379
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2259
2380
|
* ops getting through to the DeltaManager.
|
|
2260
2381
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2261
2382
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2262
2383
|
*/
|
|
2263
|
-
this.
|
|
2384
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
2264
2385
|
}
|
|
2265
2386
|
}
|
|
2266
2387
|
}
|