@fluidframework/container-loader 2.0.0-rc.3.0.3 → 2.0.0-rc.4.0.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 +17 -0
- package/api-report/container-loader.api.md +5 -1
- package/dist/attachment.d.ts +3 -2
- package/dist/attachment.d.ts.map +1 -1
- package/dist/attachment.js +5 -5
- package/dist/attachment.js.map +1 -1
- package/dist/audience.d.ts +6 -4
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +18 -3
- package/dist/audience.js.map +1 -1
- 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 +7 -3
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +57 -38
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +31 -10
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +49 -36
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +22 -13
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +145 -117
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -3
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +12 -3
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +42 -4
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +1 -2
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +5 -6
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +29 -24
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js.map +1 -1
- package/dist/error.d.ts +1 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +2 -2
- package/dist/loadPaused.d.ts +35 -0
- package/dist/loadPaused.d.ts.map +1 -0
- package/dist/loadPaused.js +115 -0
- package/dist/loadPaused.js.map +1 -0
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1 -14
- package/dist/loader.js.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +4 -4
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.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/protocol.d.ts.map +1 -1
- package/dist/protocol.js +3 -0
- package/dist/protocol.js.map +1 -1
- package/dist/public.d.ts +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/dist/serializedStateManager.d.ts +89 -9
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +150 -34
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/utils.d.ts +11 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +29 -14
- package/dist/utils.js.map +1 -1
- package/lib/attachment.d.ts +3 -2
- package/lib/attachment.d.ts.map +1 -1
- package/lib/attachment.js +5 -5
- package/lib/attachment.js.map +1 -1
- package/lib/audience.d.ts +6 -4
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +19 -4
- package/lib/audience.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 +7 -3
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +36 -17
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +31 -10
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +49 -36
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +22 -13
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +146 -118
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +3 -3
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +12 -3
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +42 -4
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +1 -2
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +5 -6
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +10 -5
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js.map +1 -1
- package/lib/error.d.ts +1 -2
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +2 -2
- package/lib/loadPaused.d.ts +35 -0
- package/lib/loadPaused.d.ts.map +1 -0
- package/lib/loadPaused.js +111 -0
- package/lib/loadPaused.js.map +1 -0
- package/lib/loader.d.ts +1 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +3 -16
- package/lib/loader.js.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.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/protocol.d.ts.map +1 -1
- package/lib/protocol.js +3 -0
- package/lib/protocol.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/serializedStateManager.d.ts +89 -9
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +146 -30
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/utils.d.ts +11 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +15 -1
- package/lib/utils.js.map +1 -1
- package/package.json +24 -21
- package/src/attachment.ts +12 -13
- package/src/audience.ts +30 -9
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +45 -22
- package/src/connectionStateHandler.ts +78 -45
- package/src/container.ts +181 -160
- package/src/containerContext.ts +2 -2
- package/src/containerStorageAdapter.ts +61 -6
- package/src/contracts.ts +5 -4
- package/src/debugLogger.ts +1 -1
- package/src/deltaManager.ts +15 -8
- package/src/deltaQueue.ts +1 -1
- package/src/error.ts +1 -1
- package/src/index.ts +1 -0
- package/src/loadPaused.ts +140 -0
- package/src/loader.ts +6 -23
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +4 -0
- package/src/retriableDocumentStorageService.ts +5 -2
- package/src/serializedStateManager.ts +215 -48
- package/src/utils.ts +19 -1
package/src/container.ts
CHANGED
|
@@ -8,8 +8,6 @@ import {
|
|
|
8
8
|
AttachState,
|
|
9
9
|
IAudience,
|
|
10
10
|
ICriticalContainerError,
|
|
11
|
-
IDeltaManager,
|
|
12
|
-
ReadOnlyInfo,
|
|
13
11
|
} from "@fluidframework/container-definitions";
|
|
14
12
|
import {
|
|
15
13
|
ContainerWarning,
|
|
@@ -27,6 +25,8 @@ import {
|
|
|
27
25
|
IProvideRuntimeFactory,
|
|
28
26
|
IRuntime,
|
|
29
27
|
isFluidCodeDetails,
|
|
28
|
+
IDeltaManager,
|
|
29
|
+
ReadOnlyInfo,
|
|
30
30
|
} from "@fluidframework/container-definitions/internal";
|
|
31
31
|
import {
|
|
32
32
|
FluidObject,
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
IUrlResolver,
|
|
48
48
|
} from "@fluidframework/driver-definitions/internal";
|
|
49
49
|
import {
|
|
50
|
+
getSnapshotTree,
|
|
50
51
|
MessageType2,
|
|
51
52
|
OnlineStatus,
|
|
52
53
|
isCombinedAppAndProtocolSummary,
|
|
@@ -75,8 +76,9 @@ import {
|
|
|
75
76
|
MessageType,
|
|
76
77
|
SummaryType,
|
|
77
78
|
} from "@fluidframework/protocol-definitions";
|
|
78
|
-
import { ITelemetryLoggerExt, type TelemetryEventCategory } from "@fluidframework/telemetry-utils";
|
|
79
79
|
import {
|
|
80
|
+
type TelemetryEventCategory,
|
|
81
|
+
ITelemetryLoggerExt,
|
|
80
82
|
EventEmitterWithErrorHandling,
|
|
81
83
|
GenericError,
|
|
82
84
|
IFluidErrorBase,
|
|
@@ -160,14 +162,9 @@ export interface IContainerLoadProps {
|
|
|
160
162
|
readonly loadMode?: IContainerLoadMode;
|
|
161
163
|
|
|
162
164
|
/**
|
|
163
|
-
* The pending state serialized from a
|
|
165
|
+
* The pending state serialized from a previous container instance
|
|
164
166
|
*/
|
|
165
167
|
readonly pendingLocalState?: IPendingContainerState;
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Load the container to at least this sequence number.
|
|
169
|
-
*/
|
|
170
|
-
readonly loadToSequenceNumber?: number;
|
|
171
168
|
}
|
|
172
169
|
|
|
173
170
|
/**
|
|
@@ -361,18 +358,13 @@ export class Container
|
|
|
361
358
|
loadProps: IContainerLoadProps,
|
|
362
359
|
createProps: IContainerCreateProps,
|
|
363
360
|
): Promise<Container> {
|
|
364
|
-
const { version, pendingLocalState, loadMode, resolvedUrl
|
|
365
|
-
loadProps;
|
|
361
|
+
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
366
362
|
|
|
367
363
|
const container = new Container(createProps, loadProps);
|
|
368
364
|
|
|
369
|
-
const disableRecordHeapSize = container.mc.config.getBoolean(
|
|
370
|
-
"Fluid.Loader.DisableRecordHeapSize",
|
|
371
|
-
);
|
|
372
|
-
|
|
373
365
|
return PerformanceEvent.timedExecAsync(
|
|
374
366
|
container.mc.logger,
|
|
375
|
-
{ eventName: "Load" },
|
|
367
|
+
{ eventName: "Load", ...loadMode },
|
|
376
368
|
async (event) =>
|
|
377
369
|
new Promise<Container>((resolve, reject) => {
|
|
378
370
|
const defaultMode: IContainerLoadMode = { opsBeforeReturn: "cached" };
|
|
@@ -391,13 +383,13 @@ export class Container
|
|
|
391
383
|
container.on("closed", onClosed);
|
|
392
384
|
|
|
393
385
|
container
|
|
394
|
-
.load(version, mode, resolvedUrl, pendingLocalState
|
|
386
|
+
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
395
387
|
.finally(() => {
|
|
396
388
|
container.removeListener("closed", onClosed);
|
|
397
389
|
})
|
|
398
390
|
.then(
|
|
399
391
|
(props) => {
|
|
400
|
-
event.end({ ...props
|
|
392
|
+
event.end({ ...props });
|
|
401
393
|
resolve(container);
|
|
402
394
|
},
|
|
403
395
|
(error) => {
|
|
@@ -415,7 +407,6 @@ export class Container
|
|
|
415
407
|
);
|
|
416
408
|
}),
|
|
417
409
|
{ start: true, end: true, cancel: "generic" },
|
|
418
|
-
disableRecordHeapSize !== true /* recordHeapSize */,
|
|
419
410
|
);
|
|
420
411
|
}
|
|
421
412
|
|
|
@@ -442,6 +433,8 @@ export class Container
|
|
|
442
433
|
/**
|
|
443
434
|
* Create a new container in a detached state that is initialized with a
|
|
444
435
|
* snapshot from a previous detached container.
|
|
436
|
+
* @param createProps - Config options for this new container instance
|
|
437
|
+
* @param snapshot - A stringified {@link IPendingDetachedContainerState}, e.g. generated via {@link serialize}
|
|
445
438
|
*/
|
|
446
439
|
public static async rehydrateDetachedFromSnapshot(
|
|
447
440
|
createProps: IContainerCreateProps,
|
|
@@ -513,9 +506,30 @@ export class Container
|
|
|
513
506
|
// It's conceivable the container could be closed when this is called
|
|
514
507
|
// Only transition states if currently loading
|
|
515
508
|
if (this._lifecycleState === "loading") {
|
|
516
|
-
// Propagate current connection state through the system.
|
|
517
|
-
this.propagateConnectionState(true /* initial transition */);
|
|
518
509
|
this._lifecycleState = "loaded";
|
|
510
|
+
|
|
511
|
+
// Connections transitions are delayed till we are loaded.
|
|
512
|
+
// This is done by holding ops and signals until the end of load sequence
|
|
513
|
+
// (calling this.handleDeltaConnectionArg() after setLoaded() call)
|
|
514
|
+
// If this assert fires, it means our logic managing connection flow is wrong, and the logic below is also wrong.
|
|
515
|
+
assert(
|
|
516
|
+
this.connectionState !== ConnectionState.Connected,
|
|
517
|
+
0x969 /* not connected yet */,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// Propagate current connection state through the system.
|
|
521
|
+
const readonly = this.readOnlyInfo.readonly ?? false;
|
|
522
|
+
// This call does not look like needed any more, with delaying all connection-related events past loaded phase.
|
|
523
|
+
// Yet, there could be some customer code that would break if we do not deliver it.
|
|
524
|
+
// Will be removed in further PRs with proper changeset.
|
|
525
|
+
this.setContextConnectedState(false /* connected */, readonly);
|
|
526
|
+
// Deliver delayed calls to DeltaManager - we ignored "connect" events while loading.
|
|
527
|
+
const cm = this._deltaManager.connectionManager;
|
|
528
|
+
if (cm.connected) {
|
|
529
|
+
const details = cm.connectionDetails;
|
|
530
|
+
assert(details !== undefined, 0x96a /* should have details if connected */);
|
|
531
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
532
|
+
}
|
|
519
533
|
}
|
|
520
534
|
}
|
|
521
535
|
|
|
@@ -525,6 +539,10 @@ export class Container
|
|
|
525
539
|
);
|
|
526
540
|
}
|
|
527
541
|
|
|
542
|
+
protected get loaded(): boolean {
|
|
543
|
+
return this._lifecycleState === "loaded";
|
|
544
|
+
}
|
|
545
|
+
|
|
528
546
|
public get disposed(): boolean {
|
|
529
547
|
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
530
548
|
}
|
|
@@ -629,14 +647,13 @@ export class Container
|
|
|
629
647
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
630
648
|
}
|
|
631
649
|
|
|
632
|
-
private _clientId: string | undefined;
|
|
633
|
-
|
|
634
650
|
/**
|
|
635
|
-
*
|
|
636
|
-
*
|
|
651
|
+
* clientId of the latest connection. Changes only once client is connected, caught up and fully loaded.
|
|
652
|
+
* Changes to clientId are delayed through container loading sequence and delived once container is fully loaded.
|
|
653
|
+
* clientId does not reset on lost connection - old value persists until new connection is fully established.
|
|
637
654
|
*/
|
|
638
655
|
public get clientId(): string | undefined {
|
|
639
|
-
return this.
|
|
656
|
+
return this.protocolHandler.audience.getSelf()?.clientId;
|
|
640
657
|
}
|
|
641
658
|
|
|
642
659
|
private get isInteractiveClient(): boolean {
|
|
@@ -721,6 +738,7 @@ export class Container
|
|
|
721
738
|
},
|
|
722
739
|
error,
|
|
723
740
|
);
|
|
741
|
+
this.close(normalizeError(error));
|
|
724
742
|
});
|
|
725
743
|
|
|
726
744
|
const {
|
|
@@ -738,7 +756,6 @@ export class Container
|
|
|
738
756
|
|
|
739
757
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
740
758
|
const pendingLocalState = loadProps?.pendingLocalState;
|
|
741
|
-
this._clientId = pendingLocalState?.clientId;
|
|
742
759
|
|
|
743
760
|
this._canReconnect = canReconnect ?? true;
|
|
744
761
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
@@ -840,13 +857,9 @@ export class Container
|
|
|
840
857
|
{
|
|
841
858
|
logger: this.mc.logger,
|
|
842
859
|
connectionStateChanged: (value, oldState, reason) => {
|
|
843
|
-
if (value === ConnectionState.Connected) {
|
|
844
|
-
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
845
|
-
}
|
|
846
860
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
847
|
-
if (this.
|
|
861
|
+
if (this.loaded) {
|
|
848
862
|
this.propagateConnectionState(
|
|
849
|
-
false /* initial transition */,
|
|
850
863
|
value === ConnectionState.Disconnected
|
|
851
864
|
? reason
|
|
852
865
|
: undefined /* disconnectedReason */,
|
|
@@ -876,15 +889,26 @@ export class Container
|
|
|
876
889
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
877
890
|
});
|
|
878
891
|
|
|
892
|
+
// This assert is important for many reasons:
|
|
893
|
+
// 1) Cosmetic / OCE burden: It's useless to raise NoJoinOp error events, if we are loading, as that's most
|
|
894
|
+
// likely to happen if snapshot loading takes too long. During this time we are not processing ops so there is no
|
|
895
|
+
// way to move to "connected" state, and thus "NoJoin" timer would fire (see
|
|
896
|
+
// IConnectionStateHandler.logConnectionIssue() callback and related code in ConnectStateHandler class implementation).
|
|
897
|
+
// But these events do not tell us anything about connectivity pipeline / op processing pipeline,
|
|
898
|
+
// only that boot is slow, and we have events for that.
|
|
899
|
+
// 2) Doing recovery below is useless in loading mode, for the reasons described above. At the same time we can't
|
|
900
|
+
// not do it, as maybe we lost JoinSignal for "self", and when loading is done, we never move to connected
|
|
901
|
+
// state. So we would have to do (in most cases) useless infinite reconnect loop while we are loading.
|
|
902
|
+
assert(
|
|
903
|
+
this.loaded,
|
|
904
|
+
0x96b /* connection issues can be raised only after container is loaded */,
|
|
905
|
+
);
|
|
906
|
+
|
|
879
907
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
880
908
|
// to very slow op fetches and we will eventually get there.
|
|
881
|
-
// For "read" connections, we get here due to
|
|
882
|
-
//
|
|
883
|
-
|
|
884
|
-
// current state of audience.
|
|
885
|
-
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
886
|
-
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
887
|
-
if (mode === "read") {
|
|
909
|
+
// For "read" connections, we get here due to join signal for "self" not arriving on time.
|
|
910
|
+
// Attempt to recover by reconnecting.
|
|
911
|
+
if (mode === "read" && category === "error") {
|
|
888
912
|
const reason = { text: "NoJoinSignal" };
|
|
889
913
|
this.disconnectInternal(reason);
|
|
890
914
|
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
@@ -893,6 +917,9 @@ export class Container
|
|
|
893
917
|
clientShouldHaveLeft: (clientId: string) => {
|
|
894
918
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
895
919
|
},
|
|
920
|
+
onCriticalError: (error: unknown) => {
|
|
921
|
+
this.close(normalizeError(error));
|
|
922
|
+
},
|
|
896
923
|
},
|
|
897
924
|
this.deltaManager,
|
|
898
925
|
pendingLocalState?.clientId,
|
|
@@ -920,6 +947,7 @@ export class Container
|
|
|
920
947
|
detachedBlobStorage,
|
|
921
948
|
this.mc.logger,
|
|
922
949
|
pendingLocalState?.snapshotBlobs,
|
|
950
|
+
pendingLocalState?.loadedGroupIdSnapshots,
|
|
923
951
|
addProtocolSummaryIfMissing,
|
|
924
952
|
forceEnableSummarizeProtocolTree,
|
|
925
953
|
);
|
|
@@ -933,6 +961,8 @@ export class Container
|
|
|
933
961
|
this.subLogger,
|
|
934
962
|
this.storageAdapter,
|
|
935
963
|
offlineLoadEnabled,
|
|
964
|
+
this,
|
|
965
|
+
() => this.isDirty,
|
|
936
966
|
);
|
|
937
967
|
|
|
938
968
|
const isDomAvailable =
|
|
@@ -1110,6 +1140,11 @@ export class Container
|
|
|
1110
1140
|
return pendingState;
|
|
1111
1141
|
}
|
|
1112
1142
|
|
|
1143
|
+
/**
|
|
1144
|
+
* Serialize current container state required to rehydrate to the same position without dataloss.
|
|
1145
|
+
* Note: The container must already be attached. For detached containers use {@link serialize}
|
|
1146
|
+
* @returns stringified {@link IPendingContainerState} for the container
|
|
1147
|
+
*/
|
|
1113
1148
|
public async getPendingLocalState(): Promise<string> {
|
|
1114
1149
|
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
1115
1150
|
}
|
|
@@ -1128,7 +1163,7 @@ export class Container
|
|
|
1128
1163
|
this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
1129
1164
|
0x0d2 /* "resolved url should be valid Fluid url" */,
|
|
1130
1165
|
);
|
|
1131
|
-
const pendingState = await this.serializedStateManager.
|
|
1166
|
+
const pendingState = await this.serializedStateManager.getPendingLocalState(
|
|
1132
1167
|
props,
|
|
1133
1168
|
this.clientId,
|
|
1134
1169
|
this.runtime,
|
|
@@ -1141,6 +1176,12 @@ export class Container
|
|
|
1141
1176
|
return this.attachmentData.state;
|
|
1142
1177
|
}
|
|
1143
1178
|
|
|
1179
|
+
/**
|
|
1180
|
+
* Serialize current container state required to rehydrate to the same position without dataloss.
|
|
1181
|
+
* Note: The container must be detached and not closed. For attached containers use
|
|
1182
|
+
* {@link getPendingLocalState} or {@link closeAndGetPendingLocalState}
|
|
1183
|
+
* @returns stringified {@link IPendingDetachedContainerState} for the container
|
|
1184
|
+
*/
|
|
1144
1185
|
public serialize(): string {
|
|
1145
1186
|
if (this.attachmentData.state === AttachState.Attached || this.closed) {
|
|
1146
1187
|
throw new UsageError("Container must not be attached or closed.");
|
|
@@ -1281,16 +1322,16 @@ export class Container
|
|
|
1281
1322
|
throw normalizeErrorAndClose(error);
|
|
1282
1323
|
});
|
|
1283
1324
|
}
|
|
1325
|
+
|
|
1326
|
+
// If offline load is enabled, attachP will return the attach summary (in Snapshot format) so we can initialize SerializedStateManager
|
|
1284
1327
|
const snapshotWithBlobs = await attachP;
|
|
1285
1328
|
this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
|
|
1329
|
+
|
|
1286
1330
|
if (!this.closed) {
|
|
1287
|
-
this.handleDeltaConnectionArg(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
},
|
|
1292
|
-
attachProps?.deltaConnection,
|
|
1293
|
-
);
|
|
1331
|
+
this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
|
|
1332
|
+
fetchOpsFromStorage: false,
|
|
1333
|
+
reason: { text: "createDetached" },
|
|
1334
|
+
});
|
|
1294
1335
|
}
|
|
1295
1336
|
},
|
|
1296
1337
|
{ start: true, end: true, cancel: "generic" },
|
|
@@ -1343,12 +1384,12 @@ export class Container
|
|
|
1343
1384
|
0x2c6 /* "Attempting to connect() a container that is not attached" */,
|
|
1344
1385
|
);
|
|
1345
1386
|
|
|
1346
|
-
// Resume processing ops and connect to delta stream
|
|
1347
|
-
this.resumeInternal(args);
|
|
1348
|
-
|
|
1349
1387
|
// Set Auto Reconnect Mode
|
|
1350
1388
|
const mode = ReconnectMode.Enabled;
|
|
1351
1389
|
this.setAutoReconnectInternal(mode, args.reason);
|
|
1390
|
+
|
|
1391
|
+
// Resume processing ops and connect to delta stream
|
|
1392
|
+
this.resumeInternal(args);
|
|
1352
1393
|
}
|
|
1353
1394
|
|
|
1354
1395
|
public disconnect() {
|
|
@@ -1372,6 +1413,12 @@ export class Container
|
|
|
1372
1413
|
|
|
1373
1414
|
// Resume processing ops
|
|
1374
1415
|
if (this.inboundQueuePausedFromInit) {
|
|
1416
|
+
// This assert guards against possibility of ops/signals showing up too soon, while
|
|
1417
|
+
// container is not ready yet to receive them. We can hit it only if some internal code call into here,
|
|
1418
|
+
// as public API like Container.connect() can be only called when user got back container object, i.e.
|
|
1419
|
+
// it is already fully loaded.
|
|
1420
|
+
assert(this.loaded, 0x96c /* connect() can be called only in fully loaded state */);
|
|
1421
|
+
|
|
1375
1422
|
this.inboundQueuePausedFromInit = false;
|
|
1376
1423
|
this._deltaManager.inbound.resume();
|
|
1377
1424
|
this._deltaManager.inboundSignal.resume();
|
|
@@ -1509,7 +1556,6 @@ export class Container
|
|
|
1509
1556
|
loadMode: IContainerLoadMode,
|
|
1510
1557
|
resolvedUrl: IResolvedUrl,
|
|
1511
1558
|
pendingLocalState: IPendingContainerState | undefined,
|
|
1512
|
-
loadToSequenceNumber: number | undefined,
|
|
1513
1559
|
) {
|
|
1514
1560
|
const timings: Record<string, number> = { phase1: performance.now() };
|
|
1515
1561
|
this.service = await this.createDocumentService(async () =>
|
|
@@ -1534,7 +1580,7 @@ export class Container
|
|
|
1534
1580
|
|
|
1535
1581
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
1536
1582
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
1537
|
-
if (loadMode.deltaConnection === undefined
|
|
1583
|
+
if (loadMode.deltaConnection === undefined) {
|
|
1538
1584
|
this.connectToDeltaStream(connectionArgs);
|
|
1539
1585
|
}
|
|
1540
1586
|
|
|
@@ -1554,10 +1600,11 @@ export class Container
|
|
|
1554
1600
|
specifiedVersion,
|
|
1555
1601
|
supportGetSnapshotApi,
|
|
1556
1602
|
);
|
|
1603
|
+
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
|
|
1557
1604
|
this._loadedFromVersion = version;
|
|
1558
1605
|
const attributes: IDocumentAttributes = await getDocumentAttributes(
|
|
1559
1606
|
this.storageAdapter,
|
|
1560
|
-
|
|
1607
|
+
baseSnapshotTree,
|
|
1561
1608
|
);
|
|
1562
1609
|
|
|
1563
1610
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
@@ -1566,57 +1613,6 @@ export class Container
|
|
|
1566
1613
|
attributes.sequenceNumber;
|
|
1567
1614
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1568
1615
|
|
|
1569
|
-
if (loadMode.pauseAfterLoad === true) {
|
|
1570
|
-
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
1571
|
-
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
1572
|
-
assert(
|
|
1573
|
-
loadToSequenceNumber !== undefined,
|
|
1574
|
-
0x727 /* sequenceNumber should be defined */,
|
|
1575
|
-
);
|
|
1576
|
-
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
1577
|
-
// due to saved ops that may be replayed after the snapshot.
|
|
1578
|
-
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
1579
|
-
if (lastProcessedSequenceNumber > loadToSequenceNumber) {
|
|
1580
|
-
throw new Error(
|
|
1581
|
-
"Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
1587
|
-
this.forceReadonly(true);
|
|
1588
|
-
|
|
1589
|
-
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
1590
|
-
const opHandler = () => {
|
|
1591
|
-
if (loadToSequenceNumber === undefined) {
|
|
1592
|
-
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
1593
|
-
if (this.deltaManager.inbound.length !== 0) {
|
|
1594
|
-
return;
|
|
1595
|
-
}
|
|
1596
|
-
} else {
|
|
1597
|
-
// If there is a specified sequence number, keep processing until we reach it.
|
|
1598
|
-
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// Pause op processing once we have processed the desired number of ops.
|
|
1604
|
-
void this.deltaManager.inbound.pause();
|
|
1605
|
-
void this.deltaManager.outbound.pause();
|
|
1606
|
-
this.off("op", opHandler);
|
|
1607
|
-
};
|
|
1608
|
-
if (
|
|
1609
|
-
(loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
1610
|
-
this.deltaManager.lastSequenceNumber === loadToSequenceNumber
|
|
1611
|
-
) {
|
|
1612
|
-
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
1613
|
-
opHandler();
|
|
1614
|
-
} else {
|
|
1615
|
-
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1616
|
-
this.on("op", opHandler);
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1616
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1621
1617
|
// Kick off any ops fetching if required.
|
|
1622
1618
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -1629,7 +1625,6 @@ export class Container
|
|
|
1629
1625
|
lastProcessedSequenceNumber,
|
|
1630
1626
|
);
|
|
1631
1627
|
break;
|
|
1632
|
-
case "sequenceNumber":
|
|
1633
1628
|
case "cached":
|
|
1634
1629
|
case "all":
|
|
1635
1630
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
@@ -1647,14 +1642,21 @@ export class Container
|
|
|
1647
1642
|
await this.initializeProtocolStateFromSnapshot(
|
|
1648
1643
|
attributes,
|
|
1649
1644
|
this.storageAdapter,
|
|
1650
|
-
|
|
1645
|
+
baseSnapshotTree,
|
|
1651
1646
|
);
|
|
1652
1647
|
|
|
1648
|
+
// If we are loading from pending state, we start with old clientId.
|
|
1649
|
+
// We switch to latest connection clientId only after setLoaded().
|
|
1650
|
+
assert(this.clientId === undefined, 0x96d /* there should be no clientId yet */);
|
|
1651
|
+
if (pendingLocalState?.clientId !== undefined) {
|
|
1652
|
+
this.protocolHandler.audience.setCurrentClientId(pendingLocalState?.clientId);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1653
1655
|
timings.phase3 = performance.now();
|
|
1654
1656
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1655
1657
|
await this.instantiateRuntime(
|
|
1656
1658
|
codeDetails,
|
|
1657
|
-
|
|
1659
|
+
baseSnapshotTree,
|
|
1658
1660
|
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1659
1661
|
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
|
|
1660
1662
|
isInstanceOfISnapshot(baseSnapshot) ? baseSnapshot : undefined,
|
|
@@ -1672,6 +1674,7 @@ export class Container
|
|
|
1672
1674
|
await this.runtime.notifyOpReplay?.(message);
|
|
1673
1675
|
}
|
|
1674
1676
|
pendingLocalState.savedOps = [];
|
|
1677
|
+
this.storageAdapter.clearPendingState();
|
|
1675
1678
|
}
|
|
1676
1679
|
|
|
1677
1680
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
@@ -1695,27 +1698,12 @@ export class Container
|
|
|
1695
1698
|
this._deltaManager.inbound.pause();
|
|
1696
1699
|
}
|
|
1697
1700
|
|
|
1698
|
-
this
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1701
|
+
// Internal context is fully loaded at this point
|
|
1702
|
+
// Move to loaded before calling this.handleDeltaConnectionArg() - latter allows ops & signals in, which
|
|
1703
|
+
// may result in container moving to "connected" state. Such transitions are allowed only in loaded state.
|
|
1704
|
+
this.setLoaded();
|
|
1704
1705
|
|
|
1705
|
-
|
|
1706
|
-
if (
|
|
1707
|
-
loadToSequenceNumber !== undefined &&
|
|
1708
|
-
this.deltaManager.lastSequenceNumber < loadToSequenceNumber
|
|
1709
|
-
) {
|
|
1710
|
-
await new Promise<void>((resolve, reject) => {
|
|
1711
|
-
const opHandler = (message: ISequencedDocumentMessage) => {
|
|
1712
|
-
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1713
|
-
resolve();
|
|
1714
|
-
this.off("op", opHandler);
|
|
1715
|
-
}
|
|
1716
|
-
};
|
|
1717
|
-
this.on("op", opHandler);
|
|
1718
|
-
});
|
|
1706
|
+
this.handleDeltaConnectionArg(loadMode.deltaConnection);
|
|
1719
1707
|
}
|
|
1720
1708
|
|
|
1721
1709
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
@@ -1727,8 +1715,6 @@ export class Container
|
|
|
1727
1715
|
throw new Error("Container was closed while load()");
|
|
1728
1716
|
}
|
|
1729
1717
|
|
|
1730
|
-
// Internal context is fully loaded at this point
|
|
1731
|
-
this.setLoaded();
|
|
1732
1718
|
timings.end = performance.now();
|
|
1733
1719
|
this.subLogger.sendTelemetryEvent(
|
|
1734
1720
|
{
|
|
@@ -2006,7 +1992,22 @@ export class Container
|
|
|
2006
1992
|
|
|
2007
1993
|
deltaManager.on("connect", (details: IConnectionDetailsInternal, _opsBehind?: number) => {
|
|
2008
1994
|
assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
2009
|
-
|
|
1995
|
+
|
|
1996
|
+
// Delay raising events until setLoaded()
|
|
1997
|
+
// Here are some of the reasons why this design is chosen:
|
|
1998
|
+
// 1. Various processes track speed of connection. But we are not processing ops or signal while container is loading,
|
|
1999
|
+
// and thus we can't move forward across connection modes. This results in telemetry errors (like NoJoinOp) that
|
|
2000
|
+
// have nothing to do with connection flow itself
|
|
2001
|
+
// 2. This also makes it hard to reason about recovery (like reconnection) in case we might have lost JoinSignal. Reconnecting
|
|
2002
|
+
// in loading phase is useless (get back to same state), but at the same time not doing it may result in broken connection
|
|
2003
|
+
// without recovery (after we loaded).
|
|
2004
|
+
// 3. We expose non-consistent view. ContainerRuntime may start loading in non-connected state, but end in connected, with
|
|
2005
|
+
// no events telling about it (until we loaded). Most of the code relies on a fact that state changes when events fire.
|
|
2006
|
+
// This will not delay any processes (as observed by the user). I.e. once container moves to loaded phase,
|
|
2007
|
+
// we immediately would transition across all phases, if we have proper signals / ops ready.
|
|
2008
|
+
if (this.loaded) {
|
|
2009
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
2010
|
+
}
|
|
2010
2011
|
});
|
|
2011
2012
|
|
|
2012
2013
|
deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
@@ -2019,8 +2020,13 @@ export class Container
|
|
|
2019
2020
|
|
|
2020
2021
|
deltaManager.on("disconnect", (text, error) => {
|
|
2021
2022
|
this.noopHeuristic?.notifyDisconnect();
|
|
2022
|
-
|
|
2023
|
-
|
|
2023
|
+
const reason = { text, error };
|
|
2024
|
+
// Symmetry with "connect" events
|
|
2025
|
+
if (this.loaded) {
|
|
2026
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
2027
|
+
} else if (!this.closed) {
|
|
2028
|
+
// Raise cancellation to get state machine back to initial state
|
|
2029
|
+
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
2024
2030
|
}
|
|
2025
2031
|
});
|
|
2026
2032
|
|
|
@@ -2035,10 +2041,12 @@ export class Container
|
|
|
2035
2041
|
});
|
|
2036
2042
|
|
|
2037
2043
|
deltaManager.on("readonly", (readonly) => {
|
|
2038
|
-
this.
|
|
2039
|
-
this.
|
|
2040
|
-
|
|
2041
|
-
|
|
2044
|
+
if (this.loaded) {
|
|
2045
|
+
this.setContextConnectedState(
|
|
2046
|
+
this.connectionState === ConnectionState.Connected,
|
|
2047
|
+
readonly,
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2042
2050
|
this.emit("readonly", readonly);
|
|
2043
2051
|
});
|
|
2044
2052
|
|
|
@@ -2055,7 +2063,7 @@ export class Container
|
|
|
2055
2063
|
|
|
2056
2064
|
private async attachDeltaManagerOpHandler(
|
|
2057
2065
|
attributes: IDocumentAttributes,
|
|
2058
|
-
prefetchType?: "
|
|
2066
|
+
prefetchType?: "cached" | "all" | "none",
|
|
2059
2067
|
lastProcessedSequenceNumber?: number,
|
|
2060
2068
|
) {
|
|
2061
2069
|
return this._deltaManager.attachOpHandler(
|
|
@@ -2098,10 +2106,7 @@ export class Container
|
|
|
2098
2106
|
// This info is of most interesting while Catching Up.
|
|
2099
2107
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
2100
2108
|
// Need to check that we have already loaded and fetched the snapshot.
|
|
2101
|
-
if (
|
|
2102
|
-
this.deltaManager.hasCheckpointSequenceNumber &&
|
|
2103
|
-
this._lifecycleState === "loaded"
|
|
2104
|
-
) {
|
|
2109
|
+
if (this.deltaManager.hasCheckpointSequenceNumber && this.loaded) {
|
|
2105
2110
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
2106
2111
|
}
|
|
2107
2112
|
}
|
|
@@ -2117,7 +2122,7 @@ export class Container
|
|
|
2117
2122
|
reason: reason?.text,
|
|
2118
2123
|
connectionInitiationReason,
|
|
2119
2124
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
2120
|
-
clientId: this.clientId,
|
|
2125
|
+
clientId: this.connectionStateHandler.clientId,
|
|
2121
2126
|
autoReconnect,
|
|
2122
2127
|
opsBehind,
|
|
2123
2128
|
online: OnlineStatus[isOnline()],
|
|
@@ -2138,27 +2143,35 @@ export class Container
|
|
|
2138
2143
|
}
|
|
2139
2144
|
}
|
|
2140
2145
|
|
|
2141
|
-
private propagateConnectionState(
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2146
|
+
private propagateConnectionState(disconnectedReason?: IConnectionStateChangeReason) {
|
|
2147
|
+
const connected = this.connectionState === ConnectionState.Connected;
|
|
2148
|
+
|
|
2149
|
+
if (connected) {
|
|
2150
|
+
const clientId = this.connectionStateHandler.clientId;
|
|
2151
|
+
assert(clientId !== undefined, 0x96e /* there has to be clientId */);
|
|
2152
|
+
this.protocolHandler.audience.setCurrentClientId(clientId);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// We communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
2147
2156
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
2148
2157
|
if (
|
|
2149
|
-
!initialTransition &&
|
|
2150
2158
|
this.connectionState !== ConnectionState.Connected &&
|
|
2151
2159
|
this.connectionState !== ConnectionState.Disconnected
|
|
2152
2160
|
) {
|
|
2153
2161
|
return;
|
|
2154
2162
|
}
|
|
2155
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
2156
2163
|
|
|
2157
2164
|
// Both protocol and context should not be undefined if we got so far.
|
|
2158
2165
|
|
|
2159
|
-
this.setContextConnectedState(
|
|
2160
|
-
this.protocolHandler.setConnectionState(
|
|
2161
|
-
raiseConnectedEvent(
|
|
2166
|
+
this.setContextConnectedState(connected, this.readOnlyInfo.readonly ?? false);
|
|
2167
|
+
this.protocolHandler.setConnectionState(connected, this.clientId);
|
|
2168
|
+
raiseConnectedEvent(
|
|
2169
|
+
this.mc.logger,
|
|
2170
|
+
this,
|
|
2171
|
+
connected,
|
|
2172
|
+
this.clientId,
|
|
2173
|
+
disconnectedReason?.text,
|
|
2174
|
+
);
|
|
2162
2175
|
}
|
|
2163
2176
|
|
|
2164
2177
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -2396,29 +2409,37 @@ export class Container
|
|
|
2396
2409
|
/**
|
|
2397
2410
|
* Set the connected state of the ContainerContext
|
|
2398
2411
|
* This controls the "connected" state of the ContainerRuntime as well
|
|
2399
|
-
* @param
|
|
2412
|
+
* @param connected - Is the container currently connected?
|
|
2400
2413
|
* @param readonly - Is the container in readonly mode?
|
|
2401
2414
|
*/
|
|
2402
|
-
private setContextConnectedState(
|
|
2403
|
-
if (this._runtime?.disposed === false) {
|
|
2415
|
+
private setContextConnectedState(connected: boolean, readonly: boolean): void {
|
|
2416
|
+
if (this._runtime?.disposed === false && this.loaded) {
|
|
2404
2417
|
/**
|
|
2405
2418
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2406
2419
|
* ops getting through to the DeltaManager.
|
|
2407
2420
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2408
2421
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2409
2422
|
*/
|
|
2410
|
-
this.runtime.setConnectionState(
|
|
2423
|
+
this.runtime.setConnectionState(connected && !readonly, this.clientId);
|
|
2411
2424
|
}
|
|
2412
2425
|
}
|
|
2413
2426
|
|
|
2414
2427
|
private handleDeltaConnectionArg(
|
|
2415
|
-
connectionArgs: IConnectionArgs,
|
|
2416
2428
|
deltaConnectionArg?: "none" | "delayed",
|
|
2417
|
-
|
|
2429
|
+
connectionArgs?: IConnectionArgs,
|
|
2418
2430
|
) {
|
|
2431
|
+
// This ensures that we allow transitions to "connected" state only after container has been fully loaded
|
|
2432
|
+
// and we propagate such events to container runtime. All events prior to being loaded are ignored.
|
|
2433
|
+
// This means if we get here in non-loaded state, we might not deliver proper events to container runtime,
|
|
2434
|
+
// and runtime implementation may miss such events.
|
|
2435
|
+
assert(
|
|
2436
|
+
this.loaded,
|
|
2437
|
+
0x96f /* has to be called after container transitions to loaded state */,
|
|
2438
|
+
);
|
|
2439
|
+
|
|
2419
2440
|
switch (deltaConnectionArg) {
|
|
2420
2441
|
case undefined:
|
|
2421
|
-
if (
|
|
2442
|
+
if (connectionArgs) {
|
|
2422
2443
|
// connect to delta stream now since we did not before
|
|
2423
2444
|
this.connectToDeltaStream(connectionArgs);
|
|
2424
2445
|
}
|