@fluidframework/container-loader 2.0.0-dev-rc.3.0.0.254674 → 2.0.0-dev-rc.4.0.0.261659
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 +33 -0
- package/api-report/container-loader.api.md +7 -3
- 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/connectionManager.d.ts +6 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +40 -16
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +29 -8
- 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 +6 -10
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +126 -113
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +1 -1
- 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/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 +3 -4
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +8 -3
- package/dist/deltaManager.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/{alpha.d.ts → legacy.d.ts} +5 -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 +0 -13
- package/dist/loader.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 +2 -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 +23 -5
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +72 -22
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -3
- package/dist/utils.js.map +1 -1
- package/{dist/beta.d.ts → internal.d.ts} +2 -4
- package/{lib/beta.d.ts → legacy.d.ts} +2 -4
- 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/connectionManager.d.ts +6 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +41 -17
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +29 -8
- 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 +6 -10
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +126 -113
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +1 -1
- 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/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 +3 -4
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +9 -4
- package/lib/deltaManager.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/{alpha.d.ts → legacy.d.ts} +5 -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 +1 -14
- package/lib/loader.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 +2 -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 +23 -5
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +66 -16
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/utils.d.ts +2 -2
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -3
- package/lib/utils.js.map +1 -1
- package/package.json +29 -27
- package/src/audience.ts +30 -9
- package/src/connectionManager.ts +50 -21
- package/src/connectionStateHandler.ts +76 -43
- package/src/container.ts +150 -153
- package/src/containerContext.ts +1 -1
- package/src/containerStorageAdapter.ts +59 -7
- package/src/debugLogger.ts +1 -1
- package/src/deltaManager.ts +13 -6
- package/src/error.ts +1 -1
- package/src/index.ts +1 -0
- package/src/loadPaused.ts +140 -0
- package/src/loader.ts +1 -21
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +4 -0
- package/src/retriableDocumentStorageService.ts +5 -2
- package/src/serializedStateManager.ts +107 -31
- package/src/utils.ts +3 -4
package/src/container.ts
CHANGED
|
@@ -75,8 +75,9 @@ import {
|
|
|
75
75
|
MessageType,
|
|
76
76
|
SummaryType,
|
|
77
77
|
} from "@fluidframework/protocol-definitions";
|
|
78
|
-
import { ITelemetryLoggerExt, type TelemetryEventCategory } from "@fluidframework/telemetry-utils";
|
|
79
78
|
import {
|
|
79
|
+
type TelemetryEventCategory,
|
|
80
|
+
ITelemetryLoggerExt,
|
|
80
81
|
EventEmitterWithErrorHandling,
|
|
81
82
|
GenericError,
|
|
82
83
|
IFluidErrorBase,
|
|
@@ -163,11 +164,6 @@ export interface IContainerLoadProps {
|
|
|
163
164
|
* The pending state serialized from a pervious container instance
|
|
164
165
|
*/
|
|
165
166
|
readonly pendingLocalState?: IPendingContainerState;
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Load the container to at least this sequence number.
|
|
169
|
-
*/
|
|
170
|
-
readonly loadToSequenceNumber?: number;
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
/**
|
|
@@ -361,18 +357,13 @@ export class Container
|
|
|
361
357
|
loadProps: IContainerLoadProps,
|
|
362
358
|
createProps: IContainerCreateProps,
|
|
363
359
|
): Promise<Container> {
|
|
364
|
-
const { version, pendingLocalState, loadMode, resolvedUrl
|
|
365
|
-
loadProps;
|
|
360
|
+
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
366
361
|
|
|
367
362
|
const container = new Container(createProps, loadProps);
|
|
368
363
|
|
|
369
|
-
const disableRecordHeapSize = container.mc.config.getBoolean(
|
|
370
|
-
"Fluid.Loader.DisableRecordHeapSize",
|
|
371
|
-
);
|
|
372
|
-
|
|
373
364
|
return PerformanceEvent.timedExecAsync(
|
|
374
365
|
container.mc.logger,
|
|
375
|
-
{ eventName: "Load" },
|
|
366
|
+
{ eventName: "Load", ...loadMode },
|
|
376
367
|
async (event) =>
|
|
377
368
|
new Promise<Container>((resolve, reject) => {
|
|
378
369
|
const defaultMode: IContainerLoadMode = { opsBeforeReturn: "cached" };
|
|
@@ -391,13 +382,13 @@ export class Container
|
|
|
391
382
|
container.on("closed", onClosed);
|
|
392
383
|
|
|
393
384
|
container
|
|
394
|
-
.load(version, mode, resolvedUrl, pendingLocalState
|
|
385
|
+
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
395
386
|
.finally(() => {
|
|
396
387
|
container.removeListener("closed", onClosed);
|
|
397
388
|
})
|
|
398
389
|
.then(
|
|
399
390
|
(props) => {
|
|
400
|
-
event.end({ ...props
|
|
391
|
+
event.end({ ...props });
|
|
401
392
|
resolve(container);
|
|
402
393
|
},
|
|
403
394
|
(error) => {
|
|
@@ -415,7 +406,6 @@ export class Container
|
|
|
415
406
|
);
|
|
416
407
|
}),
|
|
417
408
|
{ start: true, end: true, cancel: "generic" },
|
|
418
|
-
disableRecordHeapSize !== true /* recordHeapSize */,
|
|
419
409
|
);
|
|
420
410
|
}
|
|
421
411
|
|
|
@@ -513,9 +503,27 @@ export class Container
|
|
|
513
503
|
// It's conceivable the container could be closed when this is called
|
|
514
504
|
// Only transition states if currently loading
|
|
515
505
|
if (this._lifecycleState === "loading") {
|
|
516
|
-
// Propagate current connection state through the system.
|
|
517
|
-
this.propagateConnectionState(true /* initial transition */);
|
|
518
506
|
this._lifecycleState = "loaded";
|
|
507
|
+
|
|
508
|
+
// Connections transitions are delayed till we are loaded.
|
|
509
|
+
// This is done by holding ops and signals until the end of load sequence
|
|
510
|
+
// (calling this.handleDeltaConnectionArg() after setLoaded() call)
|
|
511
|
+
// If this assert fires, it means our logic managing connection flow is wrong, and the logic below is also wrong.
|
|
512
|
+
assert(this.connectionState !== ConnectionState.Connected, "not connected yet");
|
|
513
|
+
|
|
514
|
+
// Propagate current connection state through the system.
|
|
515
|
+
const readonly = this.readOnlyInfo.readonly ?? false;
|
|
516
|
+
// This call does not look like needed any more, with delaying all connection-related events past loaded phase.
|
|
517
|
+
// Yet, there could be some customer code that would break if we do not deliver it.
|
|
518
|
+
// Will be removed in further PRs with proper changeset.
|
|
519
|
+
this.setContextConnectedState(false /* connected */, readonly);
|
|
520
|
+
// Deliver delayed calls to DeltaManager - we ignored "connect" events while loading.
|
|
521
|
+
const cm = this._deltaManager.connectionManager;
|
|
522
|
+
if (cm.connected) {
|
|
523
|
+
const details = cm.connectionDetails;
|
|
524
|
+
assert(details !== undefined, "should have details if connected");
|
|
525
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
526
|
+
}
|
|
519
527
|
}
|
|
520
528
|
}
|
|
521
529
|
|
|
@@ -525,6 +533,10 @@ export class Container
|
|
|
525
533
|
);
|
|
526
534
|
}
|
|
527
535
|
|
|
536
|
+
protected get loaded(): boolean {
|
|
537
|
+
return this._lifecycleState === "loaded";
|
|
538
|
+
}
|
|
539
|
+
|
|
528
540
|
public get disposed(): boolean {
|
|
529
541
|
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
530
542
|
}
|
|
@@ -629,14 +641,13 @@ export class Container
|
|
|
629
641
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
630
642
|
}
|
|
631
643
|
|
|
632
|
-
private _clientId: string | undefined;
|
|
633
|
-
|
|
634
644
|
/**
|
|
635
|
-
*
|
|
636
|
-
*
|
|
645
|
+
* clientId of the latest connection. Changes only once client is connected, caught up and fully loaded.
|
|
646
|
+
* Changes to clientId are delayed through container loading sequence and delived once container is fully loaded.
|
|
647
|
+
* clientId does not reset on lost connection - old value persists until new connection is fully established.
|
|
637
648
|
*/
|
|
638
649
|
public get clientId(): string | undefined {
|
|
639
|
-
return this.
|
|
650
|
+
return this.protocolHandler.audience.getSelf()?.clientId;
|
|
640
651
|
}
|
|
641
652
|
|
|
642
653
|
private get isInteractiveClient(): boolean {
|
|
@@ -721,6 +732,7 @@ export class Container
|
|
|
721
732
|
},
|
|
722
733
|
error,
|
|
723
734
|
);
|
|
735
|
+
this.close(normalizeError(error));
|
|
724
736
|
});
|
|
725
737
|
|
|
726
738
|
const {
|
|
@@ -738,7 +750,6 @@ export class Container
|
|
|
738
750
|
|
|
739
751
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
740
752
|
const pendingLocalState = loadProps?.pendingLocalState;
|
|
741
|
-
this._clientId = pendingLocalState?.clientId;
|
|
742
753
|
|
|
743
754
|
this._canReconnect = canReconnect ?? true;
|
|
744
755
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
@@ -840,13 +851,9 @@ export class Container
|
|
|
840
851
|
{
|
|
841
852
|
logger: this.mc.logger,
|
|
842
853
|
connectionStateChanged: (value, oldState, reason) => {
|
|
843
|
-
if (value === ConnectionState.Connected) {
|
|
844
|
-
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
845
|
-
}
|
|
846
854
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
847
|
-
if (this.
|
|
855
|
+
if (this.loaded) {
|
|
848
856
|
this.propagateConnectionState(
|
|
849
|
-
false /* initial transition */,
|
|
850
857
|
value === ConnectionState.Disconnected
|
|
851
858
|
? reason
|
|
852
859
|
: undefined /* disconnectedReason */,
|
|
@@ -876,15 +883,26 @@ export class Container
|
|
|
876
883
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
877
884
|
});
|
|
878
885
|
|
|
886
|
+
// This assert is important for many reasons:
|
|
887
|
+
// 1) Cosmetic / OCE burden: It's useless to raise NoJoinOp error events, if we are loading, as that's most
|
|
888
|
+
// likely to happen if snapshot loading takes too long. During this time we are not processing ops so there is no
|
|
889
|
+
// way to move to "connected" state, and thus "NoJoin" timer would fire (see
|
|
890
|
+
// IConnectionStateHandler.logConnectionIssue() callback and related code in ConnectStateHandler class implementation).
|
|
891
|
+
// But these events do not tell us anything about connectivity pipeline / op processing pipeline,
|
|
892
|
+
// only that boot is slow, and we have events for that.
|
|
893
|
+
// 2) Doing recovery below is useless in loading mode, for the reasons described above. At the same time we can't
|
|
894
|
+
// not do it, as maybe we lost JoinSignal for "self", and when loading is done, we never move to connected
|
|
895
|
+
// state. So we would have to do (in most cases) useless infinite reconnect loop while we are loading.
|
|
896
|
+
assert(
|
|
897
|
+
this.loaded,
|
|
898
|
+
"connection issues can be raised only after container is loaded",
|
|
899
|
+
);
|
|
900
|
+
|
|
879
901
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
880
902
|
// 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") {
|
|
903
|
+
// For "read" connections, we get here due to join signal for "self" not arriving on time.
|
|
904
|
+
// Attempt to recover by reconnecting.
|
|
905
|
+
if (mode === "read" && category === "error") {
|
|
888
906
|
const reason = { text: "NoJoinSignal" };
|
|
889
907
|
this.disconnectInternal(reason);
|
|
890
908
|
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
@@ -893,6 +911,9 @@ export class Container
|
|
|
893
911
|
clientShouldHaveLeft: (clientId: string) => {
|
|
894
912
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
895
913
|
},
|
|
914
|
+
onCriticalError: (error: unknown) => {
|
|
915
|
+
this.close(normalizeError(error));
|
|
916
|
+
},
|
|
896
917
|
},
|
|
897
918
|
this.deltaManager,
|
|
898
919
|
pendingLocalState?.clientId,
|
|
@@ -920,6 +941,7 @@ export class Container
|
|
|
920
941
|
detachedBlobStorage,
|
|
921
942
|
this.mc.logger,
|
|
922
943
|
pendingLocalState?.snapshotBlobs,
|
|
944
|
+
pendingLocalState?.loadedGroupIdSnapshots,
|
|
923
945
|
addProtocolSummaryIfMissing,
|
|
924
946
|
forceEnableSummarizeProtocolTree,
|
|
925
947
|
);
|
|
@@ -933,6 +955,8 @@ export class Container
|
|
|
933
955
|
this.subLogger,
|
|
934
956
|
this.storageAdapter,
|
|
935
957
|
offlineLoadEnabled,
|
|
958
|
+
this,
|
|
959
|
+
() => this.isDirty,
|
|
936
960
|
);
|
|
937
961
|
|
|
938
962
|
const isDomAvailable =
|
|
@@ -1284,13 +1308,10 @@ export class Container
|
|
|
1284
1308
|
const snapshotWithBlobs = await attachP;
|
|
1285
1309
|
this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
|
|
1286
1310
|
if (!this.closed) {
|
|
1287
|
-
this.handleDeltaConnectionArg(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
},
|
|
1292
|
-
attachProps?.deltaConnection,
|
|
1293
|
-
);
|
|
1311
|
+
this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
|
|
1312
|
+
fetchOpsFromStorage: false,
|
|
1313
|
+
reason: { text: "createDetached" },
|
|
1314
|
+
});
|
|
1294
1315
|
}
|
|
1295
1316
|
},
|
|
1296
1317
|
{ start: true, end: true, cancel: "generic" },
|
|
@@ -1343,12 +1364,12 @@ export class Container
|
|
|
1343
1364
|
0x2c6 /* "Attempting to connect() a container that is not attached" */,
|
|
1344
1365
|
);
|
|
1345
1366
|
|
|
1346
|
-
// Resume processing ops and connect to delta stream
|
|
1347
|
-
this.resumeInternal(args);
|
|
1348
|
-
|
|
1349
1367
|
// Set Auto Reconnect Mode
|
|
1350
1368
|
const mode = ReconnectMode.Enabled;
|
|
1351
1369
|
this.setAutoReconnectInternal(mode, args.reason);
|
|
1370
|
+
|
|
1371
|
+
// Resume processing ops and connect to delta stream
|
|
1372
|
+
this.resumeInternal(args);
|
|
1352
1373
|
}
|
|
1353
1374
|
|
|
1354
1375
|
public disconnect() {
|
|
@@ -1372,6 +1393,12 @@ export class Container
|
|
|
1372
1393
|
|
|
1373
1394
|
// Resume processing ops
|
|
1374
1395
|
if (this.inboundQueuePausedFromInit) {
|
|
1396
|
+
// This assert guards against possibility of ops/signals showing up too soon, while
|
|
1397
|
+
// container is not ready yet to receive them. We can hit it only if some internal code call into here,
|
|
1398
|
+
// as public API like Container.connect() can be only called when user got back container object, i.e.
|
|
1399
|
+
// it is already fully loaded.
|
|
1400
|
+
assert(this.loaded, "connect() can be called only in fully loaded state");
|
|
1401
|
+
|
|
1375
1402
|
this.inboundQueuePausedFromInit = false;
|
|
1376
1403
|
this._deltaManager.inbound.resume();
|
|
1377
1404
|
this._deltaManager.inboundSignal.resume();
|
|
@@ -1509,7 +1536,6 @@ export class Container
|
|
|
1509
1536
|
loadMode: IContainerLoadMode,
|
|
1510
1537
|
resolvedUrl: IResolvedUrl,
|
|
1511
1538
|
pendingLocalState: IPendingContainerState | undefined,
|
|
1512
|
-
loadToSequenceNumber: number | undefined,
|
|
1513
1539
|
) {
|
|
1514
1540
|
const timings: Record<string, number> = { phase1: performance.now() };
|
|
1515
1541
|
this.service = await this.createDocumentService(async () =>
|
|
@@ -1534,7 +1560,7 @@ export class Container
|
|
|
1534
1560
|
|
|
1535
1561
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
1536
1562
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
1537
|
-
if (loadMode.deltaConnection === undefined
|
|
1563
|
+
if (loadMode.deltaConnection === undefined) {
|
|
1538
1564
|
this.connectToDeltaStream(connectionArgs);
|
|
1539
1565
|
}
|
|
1540
1566
|
|
|
@@ -1566,57 +1592,6 @@ export class Container
|
|
|
1566
1592
|
attributes.sequenceNumber;
|
|
1567
1593
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1568
1594
|
|
|
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
1595
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1621
1596
|
// Kick off any ops fetching if required.
|
|
1622
1597
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -1629,7 +1604,6 @@ export class Container
|
|
|
1629
1604
|
lastProcessedSequenceNumber,
|
|
1630
1605
|
);
|
|
1631
1606
|
break;
|
|
1632
|
-
case "sequenceNumber":
|
|
1633
1607
|
case "cached":
|
|
1634
1608
|
case "all":
|
|
1635
1609
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
@@ -1650,6 +1624,13 @@ export class Container
|
|
|
1650
1624
|
baseSnapshot,
|
|
1651
1625
|
);
|
|
1652
1626
|
|
|
1627
|
+
// If we are loading from pending state, we start with old clientId.
|
|
1628
|
+
// We switch to latest connection clientId only after setLoaded().
|
|
1629
|
+
assert(this.clientId === undefined, "there should be no clientId yet");
|
|
1630
|
+
if (pendingLocalState?.clientId !== undefined) {
|
|
1631
|
+
this.protocolHandler.audience.setCurrentClientId(pendingLocalState?.clientId);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1653
1634
|
timings.phase3 = performance.now();
|
|
1654
1635
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1655
1636
|
await this.instantiateRuntime(
|
|
@@ -1672,6 +1653,7 @@ export class Container
|
|
|
1672
1653
|
await this.runtime.notifyOpReplay?.(message);
|
|
1673
1654
|
}
|
|
1674
1655
|
pendingLocalState.savedOps = [];
|
|
1656
|
+
this.storageAdapter.clearPendingState();
|
|
1675
1657
|
}
|
|
1676
1658
|
|
|
1677
1659
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
@@ -1695,27 +1677,12 @@ export class Container
|
|
|
1695
1677
|
this._deltaManager.inbound.pause();
|
|
1696
1678
|
}
|
|
1697
1679
|
|
|
1698
|
-
this
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1680
|
+
// Internal context is fully loaded at this point
|
|
1681
|
+
// Move to loaded before calling this.handleDeltaConnectionArg() - latter allows ops & signals in, which
|
|
1682
|
+
// may result in container moving to "connected" state. Such transitions are allowed only in loaded state.
|
|
1683
|
+
this.setLoaded();
|
|
1704
1684
|
|
|
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
|
-
});
|
|
1685
|
+
this.handleDeltaConnectionArg(loadMode.deltaConnection);
|
|
1719
1686
|
}
|
|
1720
1687
|
|
|
1721
1688
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
@@ -1727,8 +1694,6 @@ export class Container
|
|
|
1727
1694
|
throw new Error("Container was closed while load()");
|
|
1728
1695
|
}
|
|
1729
1696
|
|
|
1730
|
-
// Internal context is fully loaded at this point
|
|
1731
|
-
this.setLoaded();
|
|
1732
1697
|
timings.end = performance.now();
|
|
1733
1698
|
this.subLogger.sendTelemetryEvent(
|
|
1734
1699
|
{
|
|
@@ -2006,7 +1971,22 @@ export class Container
|
|
|
2006
1971
|
|
|
2007
1972
|
deltaManager.on("connect", (details: IConnectionDetailsInternal, _opsBehind?: number) => {
|
|
2008
1973
|
assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
2009
|
-
|
|
1974
|
+
|
|
1975
|
+
// Delay raising events until setLoaded()
|
|
1976
|
+
// Here are some of the reasons why this design is chosen:
|
|
1977
|
+
// 1. Various processes track speed of connection. But we are not processing ops or signal while container is loading,
|
|
1978
|
+
// and thus we can't move forward across connection modes. This results in telemetry errors (like NoJoinOp) that
|
|
1979
|
+
// have nothing to do with connection flow itself
|
|
1980
|
+
// 2. This also makes it hard to reason about recovery (like reconnection) in case we might have lost JoinSignal. Reconnecting
|
|
1981
|
+
// in loading phase is useless (get back to same state), but at the same time not doing it may result in broken connection
|
|
1982
|
+
// without recovery (after we loaded).
|
|
1983
|
+
// 3. We expose non-consistent view. ContainerRuntime may start loading in non-connected state, but end in connected, with
|
|
1984
|
+
// no events telling about it (until we loaded). Most of the code relies on a fact that state changes when events fire.
|
|
1985
|
+
// This will not delay any processes (as observed by the user). I.e. once container moves to loaded phase,
|
|
1986
|
+
// we immediately would transition across all phases, if we have proper signals / ops ready.
|
|
1987
|
+
if (this.loaded) {
|
|
1988
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1989
|
+
}
|
|
2010
1990
|
});
|
|
2011
1991
|
|
|
2012
1992
|
deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
@@ -2019,8 +1999,13 @@ export class Container
|
|
|
2019
1999
|
|
|
2020
2000
|
deltaManager.on("disconnect", (text, error) => {
|
|
2021
2001
|
this.noopHeuristic?.notifyDisconnect();
|
|
2022
|
-
|
|
2023
|
-
|
|
2002
|
+
const reason = { text, error };
|
|
2003
|
+
// Symmetry with "connect" events
|
|
2004
|
+
if (this.loaded) {
|
|
2005
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
2006
|
+
} else if (!this.closed) {
|
|
2007
|
+
// Raise cancellation to get state machine back to initial state
|
|
2008
|
+
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
2024
2009
|
}
|
|
2025
2010
|
});
|
|
2026
2011
|
|
|
@@ -2035,10 +2020,12 @@ export class Container
|
|
|
2035
2020
|
});
|
|
2036
2021
|
|
|
2037
2022
|
deltaManager.on("readonly", (readonly) => {
|
|
2038
|
-
this.
|
|
2039
|
-
this.
|
|
2040
|
-
|
|
2041
|
-
|
|
2023
|
+
if (this.loaded) {
|
|
2024
|
+
this.setContextConnectedState(
|
|
2025
|
+
this.connectionState === ConnectionState.Connected,
|
|
2026
|
+
readonly,
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2042
2029
|
this.emit("readonly", readonly);
|
|
2043
2030
|
});
|
|
2044
2031
|
|
|
@@ -2055,7 +2042,7 @@ export class Container
|
|
|
2055
2042
|
|
|
2056
2043
|
private async attachDeltaManagerOpHandler(
|
|
2057
2044
|
attributes: IDocumentAttributes,
|
|
2058
|
-
prefetchType?: "
|
|
2045
|
+
prefetchType?: "cached" | "all" | "none",
|
|
2059
2046
|
lastProcessedSequenceNumber?: number,
|
|
2060
2047
|
) {
|
|
2061
2048
|
return this._deltaManager.attachOpHandler(
|
|
@@ -2098,10 +2085,7 @@ export class Container
|
|
|
2098
2085
|
// This info is of most interesting while Catching Up.
|
|
2099
2086
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
2100
2087
|
// Need to check that we have already loaded and fetched the snapshot.
|
|
2101
|
-
if (
|
|
2102
|
-
this.deltaManager.hasCheckpointSequenceNumber &&
|
|
2103
|
-
this._lifecycleState === "loaded"
|
|
2104
|
-
) {
|
|
2088
|
+
if (this.deltaManager.hasCheckpointSequenceNumber && this.loaded) {
|
|
2105
2089
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
2106
2090
|
}
|
|
2107
2091
|
}
|
|
@@ -2117,7 +2101,7 @@ export class Container
|
|
|
2117
2101
|
reason: reason?.text,
|
|
2118
2102
|
connectionInitiationReason,
|
|
2119
2103
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
2120
|
-
clientId: this.clientId,
|
|
2104
|
+
clientId: this.connectionStateHandler.clientId,
|
|
2121
2105
|
autoReconnect,
|
|
2122
2106
|
opsBehind,
|
|
2123
2107
|
online: OnlineStatus[isOnline()],
|
|
@@ -2138,27 +2122,35 @@ export class Container
|
|
|
2138
2122
|
}
|
|
2139
2123
|
}
|
|
2140
2124
|
|
|
2141
|
-
private propagateConnectionState(
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2125
|
+
private propagateConnectionState(disconnectedReason?: IConnectionStateChangeReason) {
|
|
2126
|
+
const connected = this.connectionState === ConnectionState.Connected;
|
|
2127
|
+
|
|
2128
|
+
if (connected) {
|
|
2129
|
+
const clientId = this.connectionStateHandler.clientId;
|
|
2130
|
+
assert(clientId !== undefined, "there has to be clientId");
|
|
2131
|
+
this.protocolHandler.audience.setCurrentClientId(clientId);
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// We communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
2147
2135
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
2148
2136
|
if (
|
|
2149
|
-
!initialTransition &&
|
|
2150
2137
|
this.connectionState !== ConnectionState.Connected &&
|
|
2151
2138
|
this.connectionState !== ConnectionState.Disconnected
|
|
2152
2139
|
) {
|
|
2153
2140
|
return;
|
|
2154
2141
|
}
|
|
2155
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
2156
2142
|
|
|
2157
2143
|
// Both protocol and context should not be undefined if we got so far.
|
|
2158
2144
|
|
|
2159
|
-
this.setContextConnectedState(
|
|
2160
|
-
this.protocolHandler.setConnectionState(
|
|
2161
|
-
raiseConnectedEvent(
|
|
2145
|
+
this.setContextConnectedState(connected, this.readOnlyInfo.readonly ?? false);
|
|
2146
|
+
this.protocolHandler.setConnectionState(connected, this.clientId);
|
|
2147
|
+
raiseConnectedEvent(
|
|
2148
|
+
this.mc.logger,
|
|
2149
|
+
this,
|
|
2150
|
+
connected,
|
|
2151
|
+
this.clientId,
|
|
2152
|
+
disconnectedReason?.text,
|
|
2153
|
+
);
|
|
2162
2154
|
}
|
|
2163
2155
|
|
|
2164
2156
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -2396,29 +2388,34 @@ export class Container
|
|
|
2396
2388
|
/**
|
|
2397
2389
|
* Set the connected state of the ContainerContext
|
|
2398
2390
|
* This controls the "connected" state of the ContainerRuntime as well
|
|
2399
|
-
* @param
|
|
2391
|
+
* @param connected - Is the container currently connected?
|
|
2400
2392
|
* @param readonly - Is the container in readonly mode?
|
|
2401
2393
|
*/
|
|
2402
|
-
private setContextConnectedState(
|
|
2403
|
-
if (this._runtime?.disposed === false) {
|
|
2394
|
+
private setContextConnectedState(connected: boolean, readonly: boolean): void {
|
|
2395
|
+
if (this._runtime?.disposed === false && this.loaded) {
|
|
2404
2396
|
/**
|
|
2405
2397
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2406
2398
|
* ops getting through to the DeltaManager.
|
|
2407
2399
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2408
2400
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2409
2401
|
*/
|
|
2410
|
-
this.runtime.setConnectionState(
|
|
2402
|
+
this.runtime.setConnectionState(connected && !readonly, this.clientId);
|
|
2411
2403
|
}
|
|
2412
2404
|
}
|
|
2413
2405
|
|
|
2414
2406
|
private handleDeltaConnectionArg(
|
|
2415
|
-
connectionArgs: IConnectionArgs,
|
|
2416
2407
|
deltaConnectionArg?: "none" | "delayed",
|
|
2417
|
-
|
|
2408
|
+
connectionArgs?: IConnectionArgs,
|
|
2418
2409
|
) {
|
|
2410
|
+
// This ensures that we allow transitions to "connected" state only after container has been fully loaded
|
|
2411
|
+
// and we propagate such events to container runtime. All events prior to being loaded are ignored.
|
|
2412
|
+
// This means if we get here in non-loaded state, we might not deliver proper events to container runtime,
|
|
2413
|
+
// and runtime implementation may miss such events.
|
|
2414
|
+
assert(this.loaded, "has to be called after container transitions to loaded state");
|
|
2415
|
+
|
|
2419
2416
|
switch (deltaConnectionArg) {
|
|
2420
2417
|
case undefined:
|
|
2421
|
-
if (
|
|
2418
|
+
if (connectionArgs) {
|
|
2422
2419
|
// connect to delta stream now since we did not before
|
|
2423
2420
|
this.connectToDeltaStream(connectionArgs);
|
|
2424
2421
|
}
|
package/src/containerContext.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
IVersion,
|
|
29
29
|
MessageType,
|
|
30
30
|
} from "@fluidframework/protocol-definitions";
|
|
31
|
-
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
31
|
+
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* {@inheritDoc @fluidframework/container-definitions#IContainerContext}
|