@fluidframework/container-loader 2.0.0-internal.5.1.1 → 2.0.0-internal.5.3.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 +27 -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 +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +4 -1
- 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 +30 -31
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +182 -109
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +23 -66
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +28 -213
- 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 +38 -6
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +1 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +2 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js.map +1 -1
- package/dist/disposal.d.ts +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/disposal.js +25 -0
- package/dist/disposal.js.map +1 -0
- package/dist/loader.d.ts +1 -2
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.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 +7 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +17 -19
- 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/quorum.d.ts +1 -17
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -17
- package/dist/quorum.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 +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +4 -1
- 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 +30 -31
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +186 -113
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +23 -66
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +28 -213
- 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 +38 -6
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +1 -3
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +2 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js.map +1 -1
- package/lib/disposal.d.ts +13 -0
- package/lib/disposal.d.ts.map +1 -0
- package/lib/disposal.js +21 -0
- package/lib/disposal.js.map +1 -0
- package/lib/loader.d.ts +1 -2
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.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 +7 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +15 -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/quorum.d.ts +1 -17
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +1 -16
- package/lib/quorum.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 +18 -14
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +1 -1
- package/src/connectionStateHandler.ts +15 -10
- package/src/container.ts +284 -139
- package/src/containerContext.ts +33 -335
- package/src/containerStorageAdapter.ts +47 -5
- package/src/contracts.ts +1 -3
- package/src/deltaManager.ts +15 -8
- package/src/disposal.ts +25 -0
- package/src/loader.ts +1 -1
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +18 -39
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/quorum.ts +2 -31
- 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/dist/deltaManagerProxy.d.ts +0 -42
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -79
- package/dist/deltaManagerProxy.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/lib/deltaManagerProxy.d.ts +0 -42
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -74
- package/lib/deltaManagerProxy.js.map +0 -1
- package/src/deltaManagerProxy.ts +0 -109
package/src/container.ts
CHANGED
|
@@ -7,9 +7,21 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { IEvent } from "@fluidframework/common-definitions";
|
|
11
|
+
import {
|
|
12
|
+
TypedEventEmitter,
|
|
13
|
+
assert,
|
|
14
|
+
performance,
|
|
15
|
+
unreachableCase,
|
|
16
|
+
} from "@fluidframework/common-utils";
|
|
17
|
+
import {
|
|
18
|
+
ITelemetryProperties,
|
|
19
|
+
TelemetryEventCategory,
|
|
20
|
+
IRequest,
|
|
21
|
+
IResponse,
|
|
22
|
+
IFluidRouter,
|
|
23
|
+
FluidObject,
|
|
24
|
+
} from "@fluidframework/core-interfaces";
|
|
13
25
|
import {
|
|
14
26
|
IAudience,
|
|
15
27
|
IConnectionDetailsInternal,
|
|
@@ -27,6 +39,11 @@ import {
|
|
|
27
39
|
IBatchMessage,
|
|
28
40
|
ICodeDetailsLoader,
|
|
29
41
|
IHostLoader,
|
|
42
|
+
IFluidModuleWithDetails,
|
|
43
|
+
IProvideRuntimeFactory,
|
|
44
|
+
IProvideFluidCodeDetailsComparer,
|
|
45
|
+
IFluidCodeDetailsComparer,
|
|
46
|
+
IRuntime,
|
|
30
47
|
} from "@fluidframework/container-definitions";
|
|
31
48
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
32
49
|
import {
|
|
@@ -44,11 +61,12 @@ import {
|
|
|
44
61
|
combineAppAndProtocolSummary,
|
|
45
62
|
runWithRetry,
|
|
46
63
|
isCombinedAppAndProtocolSummary,
|
|
64
|
+
MessageType2,
|
|
65
|
+
canBeCoalescedByService,
|
|
47
66
|
} from "@fluidframework/driver-utils";
|
|
48
67
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
49
68
|
import {
|
|
50
69
|
IClient,
|
|
51
|
-
IClientConfiguration,
|
|
52
70
|
IClientDetails,
|
|
53
71
|
ICommittedProposal,
|
|
54
72
|
IDocumentAttributes,
|
|
@@ -83,7 +101,6 @@ import { Audience } from "./audience";
|
|
|
83
101
|
import { ContainerContext } from "./containerContext";
|
|
84
102
|
import { ReconnectMode, IConnectionManagerFactoryArgs, getPackageName } from "./contracts";
|
|
85
103
|
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
86
|
-
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
87
104
|
import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
|
|
88
105
|
import { pkgVersion } from "./packageVersion";
|
|
89
106
|
import {
|
|
@@ -94,19 +111,16 @@ import {
|
|
|
94
111
|
} from "./containerStorageAdapter";
|
|
95
112
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
96
113
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
getCodeDetailsFromQuorumValues,
|
|
100
|
-
QuorumProxy,
|
|
101
|
-
} from "./quorum";
|
|
102
|
-
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
114
|
+
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
|
|
115
|
+
import { NoopHeuristic } from "./noopHeuristic";
|
|
103
116
|
import { ConnectionManager } from "./connectionManager";
|
|
104
117
|
import { ConnectionState } from "./connectionState";
|
|
105
118
|
import {
|
|
106
|
-
OnlyValidTermValue,
|
|
107
119
|
IProtocolHandler,
|
|
120
|
+
OnlyValidTermValue,
|
|
108
121
|
ProtocolHandler,
|
|
109
122
|
ProtocolHandlerBuilder,
|
|
123
|
+
protocolHandlerShouldProcessSignal,
|
|
110
124
|
} from "./protocol";
|
|
111
125
|
|
|
112
126
|
const detachedContainerRefSeqNumber = 0;
|
|
@@ -114,6 +128,8 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
114
128
|
const dirtyContainerEvent = "dirty";
|
|
115
129
|
const savedContainerEvent = "saved";
|
|
116
130
|
|
|
131
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
132
|
+
|
|
117
133
|
/**
|
|
118
134
|
* @internal
|
|
119
135
|
*/
|
|
@@ -336,12 +352,15 @@ export interface IPendingContainerState {
|
|
|
336
352
|
|
|
337
353
|
const summarizerClientType = "summarizer";
|
|
338
354
|
|
|
355
|
+
interface IContainerLifecycleEvents extends IEvent {
|
|
356
|
+
(event: "runtimeInstantiated", listener: () => void): void;
|
|
357
|
+
(event: "disposed", listener: () => void): void;
|
|
358
|
+
}
|
|
359
|
+
|
|
339
360
|
export class Container
|
|
340
361
|
extends EventEmitterWithErrorHandling<IContainerEvents>
|
|
341
362
|
implements IContainer, IContainerExperimental
|
|
342
363
|
{
|
|
343
|
-
public static version = "^0.1.0";
|
|
344
|
-
|
|
345
364
|
/**
|
|
346
365
|
* Load an existing container.
|
|
347
366
|
* @internal
|
|
@@ -354,6 +373,10 @@ export class Container
|
|
|
354
373
|
|
|
355
374
|
const container = new Container(createProps, loadProps);
|
|
356
375
|
|
|
376
|
+
const disableRecordHeapSize = container.mc.config.getBoolean(
|
|
377
|
+
"Fluid.Loader.DisableRecordHeapSize",
|
|
378
|
+
);
|
|
379
|
+
|
|
357
380
|
return PerformanceEvent.timedExecAsync(
|
|
358
381
|
container.mc.logger,
|
|
359
382
|
{ eventName: "Load" },
|
|
@@ -395,6 +418,7 @@ export class Container
|
|
|
395
418
|
);
|
|
396
419
|
}),
|
|
397
420
|
{ start: true, end: true, cancel: "generic" },
|
|
421
|
+
disableRecordHeapSize !== true /* recordHeapSize */,
|
|
398
422
|
);
|
|
399
423
|
}
|
|
400
424
|
|
|
@@ -447,9 +471,9 @@ export class Container
|
|
|
447
471
|
private readonly urlResolver: IUrlResolver;
|
|
448
472
|
private readonly serviceFactory: IDocumentServiceFactory;
|
|
449
473
|
private readonly codeLoader: ICodeDetailsLoader;
|
|
450
|
-
|
|
474
|
+
private readonly options: ILoaderOptions;
|
|
451
475
|
private readonly scope: FluidObject;
|
|
452
|
-
|
|
476
|
+
private readonly subLogger: TelemetryLogger;
|
|
453
477
|
private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
|
|
454
478
|
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
|
|
455
479
|
|
|
@@ -509,19 +533,16 @@ export class Container
|
|
|
509
533
|
private _attachState = AttachState.Detached;
|
|
510
534
|
|
|
511
535
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
512
|
-
public get storage(): IDocumentStorageService {
|
|
513
|
-
return this.storageAdapter;
|
|
514
|
-
}
|
|
515
536
|
|
|
516
537
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
517
538
|
private service: IDocumentService | undefined;
|
|
518
539
|
|
|
519
|
-
private
|
|
520
|
-
private get
|
|
521
|
-
if (this.
|
|
522
|
-
throw new
|
|
540
|
+
private _runtime: IRuntime | undefined;
|
|
541
|
+
private get runtime() {
|
|
542
|
+
if (this._runtime === undefined) {
|
|
543
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
523
544
|
}
|
|
524
|
-
return this.
|
|
545
|
+
return this._runtime;
|
|
525
546
|
}
|
|
526
547
|
private _protocolHandler: IProtocolHandler | undefined;
|
|
527
548
|
private get protocolHandler() {
|
|
@@ -546,10 +567,11 @@ export class Container
|
|
|
546
567
|
private lastVisible: number | undefined;
|
|
547
568
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
548
569
|
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
570
|
+
private readonly clientsWhoShouldHaveLeft = new Set<string>();
|
|
549
571
|
|
|
550
572
|
private setAutoReconnectTime = performance.now();
|
|
551
573
|
|
|
552
|
-
private
|
|
574
|
+
private noopHeuristic: NoopHeuristic | undefined;
|
|
553
575
|
|
|
554
576
|
private get connectionMode() {
|
|
555
577
|
return this._deltaManager.connectionManager.connectionMode;
|
|
@@ -574,18 +596,10 @@ export class Container
|
|
|
574
596
|
return this.service?.resolvedUrl;
|
|
575
597
|
}
|
|
576
598
|
|
|
577
|
-
public get loadedFromVersion(): IVersion | undefined {
|
|
578
|
-
return this._loadedFromVersion;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
599
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
582
600
|
return this._deltaManager.readOnlyInfo;
|
|
583
601
|
}
|
|
584
602
|
|
|
585
|
-
public get closeSignal(): AbortSignal {
|
|
586
|
-
return this._deltaManager.closeAbortController.signal;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
603
|
/**
|
|
590
604
|
* Tracks host requiring read-only mode.
|
|
591
605
|
*/
|
|
@@ -601,18 +615,10 @@ export class Container
|
|
|
601
615
|
return this.connectionStateHandler.connectionState;
|
|
602
616
|
}
|
|
603
617
|
|
|
604
|
-
|
|
618
|
+
private get connected(): boolean {
|
|
605
619
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
606
620
|
}
|
|
607
621
|
|
|
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
622
|
private _clientId: string | undefined;
|
|
617
623
|
|
|
618
624
|
/**
|
|
@@ -623,24 +629,12 @@ export class Container
|
|
|
623
629
|
return this._clientId;
|
|
624
630
|
}
|
|
625
631
|
|
|
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
632
|
private get offlineLoadEnabled(): boolean {
|
|
639
633
|
const enabled =
|
|
640
634
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
641
635
|
this.options?.enableOfflineLoad === true;
|
|
642
636
|
// summarizer will not have any pending state we want to save
|
|
643
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
637
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
644
638
|
}
|
|
645
639
|
|
|
646
640
|
/**
|
|
@@ -651,15 +645,18 @@ export class Container
|
|
|
651
645
|
return this.getCodeDetailsFromQuorum();
|
|
652
646
|
}
|
|
653
647
|
|
|
648
|
+
private _loadedCodeDetails: IFluidCodeDetails | undefined;
|
|
654
649
|
/**
|
|
655
650
|
* Get the code details that were used to load the container.
|
|
656
651
|
* @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
|
|
657
652
|
* loaded.
|
|
658
653
|
*/
|
|
659
654
|
public getLoadedCodeDetails(): IFluidCodeDetails | undefined {
|
|
660
|
-
return this.
|
|
655
|
+
return this._loadedCodeDetails;
|
|
661
656
|
}
|
|
662
657
|
|
|
658
|
+
private _loadedModule: IFluidModuleWithDetails | undefined;
|
|
659
|
+
|
|
663
660
|
/**
|
|
664
661
|
* Retrieves the audience associated with the document
|
|
665
662
|
*/
|
|
@@ -679,38 +676,33 @@ export class Container
|
|
|
679
676
|
/**
|
|
680
677
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
681
678
|
*/
|
|
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");
|
|
679
|
+
public async getEntryPoint(): Promise<FluidObject | undefined> {
|
|
680
|
+
if (this._disposed) {
|
|
681
|
+
throw new UsageError("The context is already disposed");
|
|
688
682
|
}
|
|
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
|
-
);
|
|
683
|
+
if (this._runtime !== undefined) {
|
|
684
|
+
return this._runtime.getEntryPoint?.();
|
|
708
685
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
686
|
+
return new Promise<FluidObject | undefined>((resolve, reject) => {
|
|
687
|
+
const runtimeInstantiatedHandler = () => {
|
|
688
|
+
assert(
|
|
689
|
+
this._runtime !== undefined,
|
|
690
|
+
0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
|
|
691
|
+
);
|
|
692
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
693
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
694
|
+
};
|
|
695
|
+
const disposedHandler = () => {
|
|
696
|
+
reject(new Error("ContainerContext was disposed"));
|
|
697
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
698
|
+
};
|
|
699
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
700
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
701
|
+
});
|
|
712
702
|
}
|
|
713
703
|
|
|
704
|
+
private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
|
|
705
|
+
|
|
714
706
|
/**
|
|
715
707
|
* @internal
|
|
716
708
|
*/
|
|
@@ -795,13 +787,16 @@ export class Container
|
|
|
795
787
|
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
796
788
|
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
797
789
|
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
798
|
-
containerLoadedFromVersionId: () => this.
|
|
799
|
-
containerLoadedFromVersionDate: () => this.
|
|
790
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
791
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
800
792
|
// message information to associate errors with the specific execution state
|
|
801
793
|
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
802
794
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
803
795
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
804
|
-
dmLastMsqSeqClientId: () =>
|
|
796
|
+
dmLastMsqSeqClientId: () =>
|
|
797
|
+
this.deltaManager?.lastMessage?.clientId === null
|
|
798
|
+
? "null"
|
|
799
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
805
800
|
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
806
801
|
connectionStateDuration: () =>
|
|
807
802
|
performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
@@ -866,6 +861,9 @@ export class Container
|
|
|
866
861
|
this.connect();
|
|
867
862
|
}
|
|
868
863
|
},
|
|
864
|
+
clientShouldHaveLeft: (clientId: string) => {
|
|
865
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
866
|
+
},
|
|
869
867
|
},
|
|
870
868
|
this.deltaManager,
|
|
871
869
|
pendingLocalState?.clientId,
|
|
@@ -989,6 +987,11 @@ export class Container
|
|
|
989
987
|
}
|
|
990
988
|
} finally {
|
|
991
989
|
this._lifecycleState = "closed";
|
|
990
|
+
|
|
991
|
+
// There is no user for summarizer, so we need to ensure dispose is called
|
|
992
|
+
if (this.client.details.type === summarizerClientType) {
|
|
993
|
+
this.dispose(error);
|
|
994
|
+
}
|
|
992
995
|
}
|
|
993
996
|
}
|
|
994
997
|
|
|
@@ -1005,7 +1008,8 @@ export class Container
|
|
|
1005
1008
|
this.mc.logger.sendTelemetryEvent(
|
|
1006
1009
|
{
|
|
1007
1010
|
eventName: "ContainerDispose",
|
|
1008
|
-
|
|
1011
|
+
// Only log error if container isn't closed
|
|
1012
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
1009
1013
|
},
|
|
1010
1014
|
error,
|
|
1011
1015
|
);
|
|
@@ -1019,7 +1023,8 @@ export class Container
|
|
|
1019
1023
|
|
|
1020
1024
|
this.connectionStateHandler.dispose();
|
|
1021
1025
|
|
|
1022
|
-
|
|
1026
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
1027
|
+
this._runtime?.dispose(maybeError);
|
|
1023
1028
|
|
|
1024
1029
|
this.storageAdapter.dispose();
|
|
1025
1030
|
|
|
@@ -1042,6 +1047,7 @@ export class Container
|
|
|
1042
1047
|
}
|
|
1043
1048
|
} finally {
|
|
1044
1049
|
this._lifecycleState = "disposed";
|
|
1050
|
+
this._lifecycleEvents.emit("disposed");
|
|
1045
1051
|
}
|
|
1046
1052
|
}
|
|
1047
1053
|
|
|
@@ -1058,6 +1064,11 @@ export class Container
|
|
|
1058
1064
|
if (!this.offlineLoadEnabled) {
|
|
1059
1065
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
1060
1066
|
}
|
|
1067
|
+
if (this.closed || this._disposed) {
|
|
1068
|
+
throw new UsageError(
|
|
1069
|
+
"Pending state cannot be retried if the container is closed or disposed",
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1061
1072
|
assert(
|
|
1062
1073
|
this.attachState === AttachState.Attached,
|
|
1063
1074
|
0x0d1 /* "Container should be attached before close" */,
|
|
@@ -1069,7 +1080,7 @@ export class Container
|
|
|
1069
1080
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1070
1081
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1071
1082
|
const pendingState: IPendingContainerState = {
|
|
1072
|
-
pendingRuntimeState: this.
|
|
1083
|
+
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
1073
1084
|
baseSnapshot: this.baseSnapshot,
|
|
1074
1085
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1075
1086
|
savedOps: this.savedOps,
|
|
@@ -1093,7 +1104,7 @@ export class Container
|
|
|
1093
1104
|
0x0d3 /* "Should only be called in detached container" */,
|
|
1094
1105
|
);
|
|
1095
1106
|
|
|
1096
|
-
const appSummary: ISummaryTree = this.
|
|
1107
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1097
1108
|
const protocolSummary = this.captureProtocolSummary();
|
|
1098
1109
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1099
1110
|
|
|
@@ -1139,7 +1150,7 @@ export class Container
|
|
|
1139
1150
|
if (!hasAttachmentBlobs) {
|
|
1140
1151
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
1141
1152
|
// semantics around what the attach means as far as async code goes.
|
|
1142
|
-
const appSummary: ISummaryTree = this.
|
|
1153
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1143
1154
|
const protocolSummary = this.captureProtocolSummary();
|
|
1144
1155
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1145
1156
|
|
|
@@ -1148,6 +1159,7 @@ export class Container
|
|
|
1148
1159
|
// starting to attach the container to storage.
|
|
1149
1160
|
// Also, this should only be fired in detached container.
|
|
1150
1161
|
this._attachState = AttachState.Attaching;
|
|
1162
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1151
1163
|
this.emit("attaching");
|
|
1152
1164
|
if (this.offlineLoadEnabled) {
|
|
1153
1165
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1176,7 +1188,7 @@ export class Container
|
|
|
1176
1188
|
"containerAttach",
|
|
1177
1189
|
this.mc.logger,
|
|
1178
1190
|
{
|
|
1179
|
-
cancel: this.
|
|
1191
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
1180
1192
|
}, // progress
|
|
1181
1193
|
);
|
|
1182
1194
|
}
|
|
@@ -1205,11 +1217,12 @@ export class Container
|
|
|
1205
1217
|
}
|
|
1206
1218
|
|
|
1207
1219
|
// take summary and upload
|
|
1208
|
-
const appSummary: ISummaryTree = this.
|
|
1220
|
+
const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
|
|
1209
1221
|
const protocolSummary = this.captureProtocolSummary();
|
|
1210
1222
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1211
1223
|
|
|
1212
1224
|
this._attachState = AttachState.Attaching;
|
|
1225
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1213
1226
|
this.emit("attaching");
|
|
1214
1227
|
if (this.offlineLoadEnabled) {
|
|
1215
1228
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1226,6 +1239,7 @@ export class Container
|
|
|
1226
1239
|
}
|
|
1227
1240
|
|
|
1228
1241
|
this._attachState = AttachState.Attached;
|
|
1242
|
+
this.runtime.setAttachState(AttachState.Attached);
|
|
1229
1243
|
this.emit("attached");
|
|
1230
1244
|
|
|
1231
1245
|
if (!this.closed) {
|
|
@@ -1250,7 +1264,7 @@ export class Container
|
|
|
1250
1264
|
return PerformanceEvent.timedExecAsync(
|
|
1251
1265
|
this.mc.logger,
|
|
1252
1266
|
{ eventName: "Request" },
|
|
1253
|
-
async () => this.
|
|
1267
|
+
async () => this.runtime.request(path),
|
|
1254
1268
|
{ end: true, cancel: "error" },
|
|
1255
1269
|
);
|
|
1256
1270
|
}
|
|
@@ -1335,7 +1349,7 @@ export class Container
|
|
|
1335
1349
|
this.connectToDeltaStream(args);
|
|
1336
1350
|
}
|
|
1337
1351
|
|
|
1338
|
-
public async
|
|
1352
|
+
public readonly getAbsoluteUrl = async (relativeUrl: string): Promise<string | undefined> => {
|
|
1339
1353
|
if (this.resolvedUrl === undefined) {
|
|
1340
1354
|
return undefined;
|
|
1341
1355
|
}
|
|
@@ -1343,9 +1357,9 @@ export class Container
|
|
|
1343
1357
|
return this.urlResolver.getAbsoluteUrl(
|
|
1344
1358
|
this.resolvedUrl,
|
|
1345
1359
|
relativeUrl,
|
|
1346
|
-
getPackageName(this.
|
|
1360
|
+
getPackageName(this._loadedCodeDetails),
|
|
1347
1361
|
);
|
|
1348
|
-
}
|
|
1362
|
+
};
|
|
1349
1363
|
|
|
1350
1364
|
public async proposeCodeDetails(codeDetails: IFluidCodeDetails) {
|
|
1351
1365
|
if (!isFluidCodeDetails(codeDetails)) {
|
|
@@ -1376,7 +1390,7 @@ export class Container
|
|
|
1376
1390
|
this.deltaManager.inboundSignal.pause(),
|
|
1377
1391
|
]);
|
|
1378
1392
|
|
|
1379
|
-
if ((await this.
|
|
1393
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
1380
1394
|
this.deltaManager.inbound.resume();
|
|
1381
1395
|
this.deltaManager.inboundSignal.resume();
|
|
1382
1396
|
return;
|
|
@@ -1387,6 +1401,47 @@ export class Container
|
|
|
1387
1401
|
this.close(error);
|
|
1388
1402
|
}
|
|
1389
1403
|
|
|
1404
|
+
/**
|
|
1405
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
1406
|
+
*/
|
|
1407
|
+
private async satisfies(constraintCodeDetails: IFluidCodeDetails) {
|
|
1408
|
+
// If we have no module, it can't satisfy anything.
|
|
1409
|
+
if (this._loadedModule === undefined) {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
const comparers: IFluidCodeDetailsComparer[] = [];
|
|
1414
|
+
|
|
1415
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
1416
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
1417
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
|
|
1421
|
+
this._loadedModule?.module.fluidExport;
|
|
1422
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
1423
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
1427
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
1428
|
+
// rather than potentially running with incompatible code.
|
|
1429
|
+
if (comparers.length === 0) {
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
for (const comparer of comparers) {
|
|
1434
|
+
const satisfies = await comparer.satisfies(
|
|
1435
|
+
this._loadedModule?.details,
|
|
1436
|
+
constraintCodeDetails,
|
|
1437
|
+
);
|
|
1438
|
+
if (satisfies === false) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1390
1445
|
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
1391
1446
|
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
1392
1447
|
return versions[0];
|
|
@@ -1464,7 +1519,10 @@ export class Container
|
|
|
1464
1519
|
if (this.offlineLoadEnabled) {
|
|
1465
1520
|
this.baseSnapshot = snapshot;
|
|
1466
1521
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
1467
|
-
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1522
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1523
|
+
snapshot,
|
|
1524
|
+
this.storageAdapter,
|
|
1525
|
+
);
|
|
1468
1526
|
}
|
|
1469
1527
|
}
|
|
1470
1528
|
|
|
@@ -1520,7 +1578,7 @@ export class Container
|
|
|
1520
1578
|
this.processRemoteMessage(message);
|
|
1521
1579
|
|
|
1522
1580
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
1523
|
-
await this.
|
|
1581
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
1524
1582
|
}
|
|
1525
1583
|
pendingLocalState.savedOps = [];
|
|
1526
1584
|
|
|
@@ -1872,7 +1930,7 @@ export class Container
|
|
|
1872
1930
|
});
|
|
1873
1931
|
|
|
1874
1932
|
deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
|
|
1875
|
-
this.
|
|
1933
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1876
1934
|
if (!this.closed) {
|
|
1877
1935
|
this.connectionStateHandler.receivedDisconnectEvent(reason, error);
|
|
1878
1936
|
}
|
|
@@ -1950,7 +2008,11 @@ export class Container
|
|
|
1950
2008
|
} else if (value === ConnectionState.CatchingUp) {
|
|
1951
2009
|
// This info is of most interesting while Catching Up.
|
|
1952
2010
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
1953
|
-
|
|
2011
|
+
// Need to check that we have already loaded and fetched the snapshot.
|
|
2012
|
+
if (
|
|
2013
|
+
this.deltaManager.hasCheckpointSequenceNumber &&
|
|
2014
|
+
this._lifecycleState === "loaded"
|
|
2015
|
+
) {
|
|
1954
2016
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1955
2017
|
}
|
|
1956
2018
|
}
|
|
@@ -2095,7 +2157,7 @@ export class Container
|
|
|
2095
2157
|
}
|
|
2096
2158
|
|
|
2097
2159
|
this.messageCountAfterDisconnection += 1;
|
|
2098
|
-
this.
|
|
2160
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
2099
2161
|
return this._deltaManager.submit(
|
|
2100
2162
|
type,
|
|
2101
2163
|
contents,
|
|
@@ -2112,39 +2174,67 @@ export class Container
|
|
|
2112
2174
|
}
|
|
2113
2175
|
const local = this.clientId === message.clientId;
|
|
2114
2176
|
|
|
2177
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
2178
|
+
// flagged should have left, or from a client that's not in the quorum but should be
|
|
2179
|
+
if (message.clientId != null) {
|
|
2180
|
+
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
2181
|
+
|
|
2182
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
2183
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
2184
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
2188
|
+
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
2189
|
+
// document we don't need to blow up aggressively.
|
|
2190
|
+
if (
|
|
2191
|
+
this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
2192
|
+
!canBeCoalescedByService(message)
|
|
2193
|
+
) {
|
|
2194
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
2195
|
+
throw new Error("Remote message's clientId already should have left");
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2115
2199
|
// Allow the protocol handler to process the message
|
|
2116
2200
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2117
2201
|
|
|
2118
2202
|
// Forward messages to the loaded runtime for processing
|
|
2119
|
-
this.
|
|
2203
|
+
this.runtime.process(message, local);
|
|
2120
2204
|
|
|
2121
2205
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
2122
2206
|
if (this.activeConnection()) {
|
|
2123
|
-
if (this.
|
|
2207
|
+
if (this.noopHeuristic === undefined) {
|
|
2208
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
2124
2209
|
// Note that config from first connection will be used for this container's lifetime.
|
|
2125
2210
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
2126
2211
|
// clients.
|
|
2127
2212
|
// All existing will continue to use settings they got earlier.
|
|
2128
2213
|
assert(
|
|
2129
|
-
|
|
2214
|
+
serviceConfiguration !== undefined,
|
|
2130
2215
|
0x2e4 /* "there should be service config for active connection" */,
|
|
2131
2216
|
);
|
|
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,
|
|
2217
|
+
this.noopHeuristic = new NoopHeuristic(
|
|
2218
|
+
serviceConfiguration.noopTimeFrequency,
|
|
2219
|
+
serviceConfiguration.noopCountFrequency,
|
|
2142
2220
|
);
|
|
2221
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
2222
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
2223
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
2224
|
+
// running the microtask that the heuristic queued in response.
|
|
2225
|
+
assert(
|
|
2226
|
+
this.activeConnection(),
|
|
2227
|
+
0x241 /* "Trying to send noop without active connection" */,
|
|
2228
|
+
);
|
|
2229
|
+
this.submitMessage(MessageType.NoOp);
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
2233
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
2234
|
+
if (result.immediateNoOp === true) {
|
|
2235
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
2236
|
+
this.submitMessage(MessageType2.Accept as unknown as MessageType);
|
|
2143
2237
|
}
|
|
2144
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(
|
|
2145
|
-
message,
|
|
2146
|
-
result.immediateNoOp === true,
|
|
2147
|
-
);
|
|
2148
2238
|
}
|
|
2149
2239
|
|
|
2150
2240
|
this.emit("op", message);
|
|
@@ -2156,11 +2246,11 @@ export class Container
|
|
|
2156
2246
|
|
|
2157
2247
|
private processSignal(message: ISignalMessage) {
|
|
2158
2248
|
// No clientId indicates a system signal message.
|
|
2159
|
-
if (message
|
|
2249
|
+
if (protocolHandlerShouldProcessSignal(message)) {
|
|
2160
2250
|
this.protocolHandler.processSignal(message);
|
|
2161
2251
|
} else {
|
|
2162
2252
|
const local = this.clientId === message.clientId;
|
|
2163
|
-
this.
|
|
2253
|
+
this.runtime.processSignal(message, local);
|
|
2164
2254
|
}
|
|
2165
2255
|
}
|
|
2166
2256
|
|
|
@@ -2202,23 +2292,50 @@ export class Container
|
|
|
2202
2292
|
private async instantiateContext(
|
|
2203
2293
|
existing: boolean,
|
|
2204
2294
|
codeDetails: IFluidCodeDetails,
|
|
2205
|
-
snapshot
|
|
2295
|
+
snapshot: ISnapshotTree | undefined,
|
|
2206
2296
|
pendingLocalState?: unknown,
|
|
2207
2297
|
) {
|
|
2208
|
-
assert(this.
|
|
2298
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
2209
2299
|
|
|
2210
2300
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
2211
2301
|
// are set. Global requests will still go directly to the loader
|
|
2212
2302
|
const maybeLoader: FluidObject<IHostLoader> = this.scope;
|
|
2213
2303
|
const loader = new RelativeLoader(this, maybeLoader.ILoader);
|
|
2214
|
-
|
|
2215
|
-
|
|
2304
|
+
|
|
2305
|
+
const loadCodeResult = await PerformanceEvent.timedExecAsync(
|
|
2306
|
+
this.subLogger,
|
|
2307
|
+
{ eventName: "CodeLoad" },
|
|
2308
|
+
async () => this.codeLoader.load(codeDetails),
|
|
2309
|
+
);
|
|
2310
|
+
|
|
2311
|
+
this._loadedModule = {
|
|
2312
|
+
module: loadCodeResult.module,
|
|
2313
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
2314
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
2315
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
2316
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
|
|
2320
|
+
this._loadedModule.module.fluidExport;
|
|
2321
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
2322
|
+
if (runtimeFactory === undefined) {
|
|
2323
|
+
throw new Error(packageNotFactoryError);
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const getSpecifiedCodeDetails = () =>
|
|
2327
|
+
(this.protocolHandler.quorum.get("code") ??
|
|
2328
|
+
this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
2329
|
+
|
|
2330
|
+
const context = new ContainerContext(
|
|
2331
|
+
this.options,
|
|
2216
2332
|
this.scope,
|
|
2217
|
-
this.codeLoader,
|
|
2218
|
-
codeDetails,
|
|
2219
2333
|
snapshot,
|
|
2220
|
-
|
|
2221
|
-
|
|
2334
|
+
this._loadedFromVersion,
|
|
2335
|
+
this._deltaManager,
|
|
2336
|
+
this.storageAdapter,
|
|
2337
|
+
this.protocolHandler.quorum,
|
|
2338
|
+
this.protocolHandler.audience,
|
|
2222
2339
|
loader,
|
|
2223
2340
|
(type, contents, batch, metadata) =>
|
|
2224
2341
|
this.submitContainerMessage(type, contents, batch, metadata),
|
|
@@ -2229,22 +2346,42 @@ export class Container
|
|
|
2229
2346
|
(message) => this.submitSignal(message),
|
|
2230
2347
|
(error?: ICriticalContainerError) => this.dispose(error),
|
|
2231
2348
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2232
|
-
|
|
2233
|
-
|
|
2349
|
+
this.updateDirtyContainerState,
|
|
2350
|
+
this.getAbsoluteUrl,
|
|
2351
|
+
() => this.resolvedUrl?.id,
|
|
2352
|
+
() => this.clientId,
|
|
2353
|
+
() => this._deltaManager.serviceConfiguration,
|
|
2354
|
+
() => this.attachState,
|
|
2355
|
+
() => this.connected,
|
|
2356
|
+
getSpecifiedCodeDetails,
|
|
2357
|
+
this._deltaManager.clientDetails,
|
|
2234
2358
|
existing,
|
|
2359
|
+
this.subLogger,
|
|
2235
2360
|
pendingLocalState,
|
|
2236
2361
|
);
|
|
2362
|
+
this._lifecycleEvents.once("disposed", () => {
|
|
2363
|
+
context.dispose();
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2367
|
+
this.subLogger,
|
|
2368
|
+
{ eventName: "InstantiateRuntime" },
|
|
2369
|
+
async () => runtimeFactory.instantiateRuntime(context, existing),
|
|
2370
|
+
);
|
|
2371
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
2372
|
+
|
|
2373
|
+
this._loadedCodeDetails = codeDetails;
|
|
2237
2374
|
|
|
2238
2375
|
this.emit("contextChanged", codeDetails);
|
|
2239
2376
|
}
|
|
2240
2377
|
|
|
2241
|
-
private updateDirtyContainerState(dirty: boolean) {
|
|
2378
|
+
private readonly updateDirtyContainerState = (dirty: boolean) => {
|
|
2242
2379
|
if (this._dirtyContainer === dirty) {
|
|
2243
2380
|
return;
|
|
2244
2381
|
}
|
|
2245
2382
|
this._dirtyContainer = dirty;
|
|
2246
2383
|
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
2247
|
-
}
|
|
2384
|
+
};
|
|
2248
2385
|
|
|
2249
2386
|
/**
|
|
2250
2387
|
* Set the connected state of the ContainerContext
|
|
@@ -2253,21 +2390,21 @@ export class Container
|
|
|
2253
2390
|
* @param readonly - Is the container in readonly mode?
|
|
2254
2391
|
*/
|
|
2255
2392
|
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
2256
|
-
if (this.
|
|
2393
|
+
if (this._runtime?.disposed === false) {
|
|
2257
2394
|
/**
|
|
2258
2395
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2259
2396
|
* ops getting through to the DeltaManager.
|
|
2260
2397
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2261
2398
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2262
2399
|
*/
|
|
2263
|
-
this.
|
|
2400
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
2264
2401
|
}
|
|
2265
2402
|
}
|
|
2266
2403
|
}
|
|
2267
2404
|
|
|
2268
2405
|
/**
|
|
2269
2406
|
* IContainer interface that includes experimental features still under development.
|
|
2270
|
-
* @
|
|
2407
|
+
* @experimental
|
|
2271
2408
|
*/
|
|
2272
2409
|
export interface IContainerExperimental extends IContainer {
|
|
2273
2410
|
/**
|
|
@@ -2278,5 +2415,13 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2278
2415
|
* @experimental misuse of this API can result in duplicate op submission and potential document corruption
|
|
2279
2416
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2280
2417
|
*/
|
|
2281
|
-
getPendingLocalState(): string;
|
|
2418
|
+
getPendingLocalState?(): string;
|
|
2419
|
+
|
|
2420
|
+
/**
|
|
2421
|
+
* Closes the container and returns serialized local state intended to be
|
|
2422
|
+
* given to a newly loaded container.
|
|
2423
|
+
* @experimental
|
|
2424
|
+
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2425
|
+
*/
|
|
2426
|
+
closeAndGetPendingLocalState(): string;
|
|
2282
2427
|
}
|