@fluidframework/container-loader 2.0.0-internal.5.4.0 → 2.0.0-internal.6.1.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 +85 -0
- package/dist/connectionManager.d.ts +4 -4
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +57 -49
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +15 -14
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +26 -28
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +10 -5
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +183 -134
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +2 -12
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +1 -20
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.js +3 -5
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +20 -7
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.js +2 -3
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +19 -6
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +67 -28
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.js +1 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/loader.d.ts +12 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +57 -42
- 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 +4 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +25 -4
- package/dist/protocol.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -6
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts +4 -4
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +58 -50
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +15 -14
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +26 -28
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +10 -5
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +182 -133
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +2 -12
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +1 -20
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.js +3 -5
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +20 -7
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.js +2 -3
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +19 -6
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +67 -28
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.js +1 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/loader.d.ts +12 -0
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +57 -42
- 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 +4 -2
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +25 -4
- package/lib/protocol.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +22 -5
- package/lib/utils.js.map +1 -1
- package/package.json +15 -19
- package/src/connectionManager.ts +53 -34
- package/src/connectionStateHandler.ts +30 -37
- package/src/container.ts +156 -76
- package/src/containerContext.ts +0 -24
- package/src/contracts.ts +27 -10
- package/src/deltaManager.ts +41 -18
- package/src/loader.ts +37 -23
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +33 -2
- package/src/utils.ts +29 -0
package/src/container.ts
CHANGED
|
@@ -23,46 +23,42 @@ import {
|
|
|
23
23
|
FluidObject,
|
|
24
24
|
} from "@fluidframework/core-interfaces";
|
|
25
25
|
import {
|
|
26
|
+
AttachState,
|
|
27
|
+
ContainerWarning,
|
|
26
28
|
IAudience,
|
|
27
|
-
|
|
29
|
+
IBatchMessage,
|
|
30
|
+
ICodeDetailsLoader,
|
|
28
31
|
IContainer,
|
|
29
32
|
IContainerEvents,
|
|
30
|
-
IDeltaManager,
|
|
31
|
-
ICriticalContainerError,
|
|
32
|
-
ContainerWarning,
|
|
33
|
-
AttachState,
|
|
34
|
-
IThrottlingWarning,
|
|
35
|
-
ReadOnlyInfo,
|
|
36
33
|
IContainerLoadMode,
|
|
34
|
+
ICriticalContainerError,
|
|
35
|
+
IDeltaManager,
|
|
37
36
|
IFluidCodeDetails,
|
|
38
|
-
isFluidCodeDetails,
|
|
39
|
-
IBatchMessage,
|
|
40
|
-
ICodeDetailsLoader,
|
|
41
37
|
IHostLoader,
|
|
42
38
|
IFluidModuleWithDetails,
|
|
43
39
|
IProvideRuntimeFactory,
|
|
44
40
|
IProvideFluidCodeDetailsComparer,
|
|
45
41
|
IFluidCodeDetailsComparer,
|
|
46
42
|
IRuntime,
|
|
43
|
+
ReadOnlyInfo,
|
|
44
|
+
isFluidCodeDetails,
|
|
47
45
|
} from "@fluidframework/container-definitions";
|
|
48
46
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
49
47
|
import {
|
|
50
|
-
IAnyDriverError,
|
|
51
48
|
IDocumentService,
|
|
52
49
|
IDocumentServiceFactory,
|
|
53
50
|
IDocumentStorageService,
|
|
54
51
|
IResolvedUrl,
|
|
52
|
+
IThrottlingWarning,
|
|
55
53
|
IUrlResolver,
|
|
56
54
|
} from "@fluidframework/driver-definitions";
|
|
57
55
|
import {
|
|
58
56
|
readAndParse,
|
|
59
57
|
OnlineStatus,
|
|
60
58
|
isOnline,
|
|
61
|
-
combineAppAndProtocolSummary,
|
|
62
59
|
runWithRetry,
|
|
63
60
|
isCombinedAppAndProtocolSummary,
|
|
64
61
|
MessageType2,
|
|
65
|
-
canBeCoalescedByService,
|
|
66
62
|
} from "@fluidframework/driver-utils";
|
|
67
63
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
68
64
|
import {
|
|
@@ -99,7 +95,13 @@ import {
|
|
|
99
95
|
} from "@fluidframework/telemetry-utils";
|
|
100
96
|
import { Audience } from "./audience";
|
|
101
97
|
import { ContainerContext } from "./containerContext";
|
|
102
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
ReconnectMode,
|
|
100
|
+
IConnectionManagerFactoryArgs,
|
|
101
|
+
getPackageName,
|
|
102
|
+
IConnectionDetailsInternal,
|
|
103
|
+
IConnectionStateChangeReason,
|
|
104
|
+
} from "./contracts";
|
|
103
105
|
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
104
106
|
import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
|
|
105
107
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -110,7 +112,11 @@ import {
|
|
|
110
112
|
ISerializableBlobContents,
|
|
111
113
|
} from "./containerStorageAdapter";
|
|
112
114
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
113
|
-
import {
|
|
115
|
+
import {
|
|
116
|
+
combineAppAndProtocolSummary,
|
|
117
|
+
getProtocolSnapshotTree,
|
|
118
|
+
getSnapshotTreeFromSerializedContainer,
|
|
119
|
+
} from "./utils";
|
|
114
120
|
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
115
121
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
116
122
|
import { ConnectionManager } from "./connectionManager";
|
|
@@ -151,6 +157,11 @@ export interface IContainerLoadProps {
|
|
|
151
157
|
* The pending state serialized from a pervious container instance
|
|
152
158
|
*/
|
|
153
159
|
readonly pendingLocalState?: IPendingContainerState;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Load the container to at least this sequence number.
|
|
163
|
+
*/
|
|
164
|
+
readonly loadToSequenceNumber?: number;
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
/**
|
|
@@ -369,7 +380,8 @@ export class Container
|
|
|
369
380
|
loadProps: IContainerLoadProps,
|
|
370
381
|
createProps: IContainerCreateProps,
|
|
371
382
|
): Promise<Container> {
|
|
372
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } =
|
|
383
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
|
|
384
|
+
loadProps;
|
|
373
385
|
|
|
374
386
|
const container = new Container(createProps, loadProps);
|
|
375
387
|
|
|
@@ -398,7 +410,7 @@ export class Container
|
|
|
398
410
|
container.on("closed", onClosed);
|
|
399
411
|
|
|
400
412
|
container
|
|
401
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
413
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
402
414
|
.finally(() => {
|
|
403
415
|
container.removeListener("closed", onClosed);
|
|
404
416
|
})
|
|
@@ -748,7 +760,19 @@ export class Container
|
|
|
748
760
|
this.scope = scope;
|
|
749
761
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
750
762
|
this.protocolHandlerBuilder =
|
|
751
|
-
protocolHandlerBuilder ??
|
|
763
|
+
protocolHandlerBuilder ??
|
|
764
|
+
((
|
|
765
|
+
attributes: IDocumentAttributes,
|
|
766
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
767
|
+
sendProposal: (key: string, value: any) => number,
|
|
768
|
+
) =>
|
|
769
|
+
new ProtocolHandler(
|
|
770
|
+
attributes,
|
|
771
|
+
quorumSnapshot,
|
|
772
|
+
sendProposal,
|
|
773
|
+
new Audience(),
|
|
774
|
+
(clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
|
|
775
|
+
));
|
|
752
776
|
|
|
753
777
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
754
778
|
this.clone = async (
|
|
@@ -814,11 +838,11 @@ export class Container
|
|
|
814
838
|
this.connectionStateHandler = createConnectionStateHandler(
|
|
815
839
|
{
|
|
816
840
|
logger: this.mc.logger,
|
|
817
|
-
connectionStateChanged: (value, oldState, reason
|
|
841
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
818
842
|
if (value === ConnectionState.Connected) {
|
|
819
843
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
820
844
|
}
|
|
821
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
845
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
822
846
|
if (this._lifecycleState === "loaded") {
|
|
823
847
|
this.propagateConnectionState(
|
|
824
848
|
false /* initial transition */,
|
|
@@ -860,8 +884,9 @@ export class Container
|
|
|
860
884
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
861
885
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
862
886
|
if (mode === "read") {
|
|
863
|
-
|
|
864
|
-
this.
|
|
887
|
+
const reason = { text: "NoJoinSignal" };
|
|
888
|
+
this.disconnectInternal(reason);
|
|
889
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
865
890
|
}
|
|
866
891
|
},
|
|
867
892
|
clientShouldHaveLeft: (clientId: string) => {
|
|
@@ -1054,16 +1079,21 @@ export class Container
|
|
|
1054
1079
|
}
|
|
1055
1080
|
}
|
|
1056
1081
|
|
|
1057
|
-
public closeAndGetPendingLocalState(): string {
|
|
1082
|
+
public async closeAndGetPendingLocalState(): Promise<string> {
|
|
1058
1083
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
1059
1084
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
1060
1085
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
1061
|
-
|
|
1086
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
1087
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
1062
1088
|
this.close();
|
|
1063
1089
|
return pendingState;
|
|
1064
1090
|
}
|
|
1065
1091
|
|
|
1066
|
-
public getPendingLocalState(): string {
|
|
1092
|
+
public async getPendingLocalState(): Promise<string> {
|
|
1093
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) {
|
|
1067
1097
|
if (!this.offlineLoadEnabled) {
|
|
1068
1098
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
1069
1099
|
}
|
|
@@ -1082,8 +1112,9 @@ export class Container
|
|
|
1082
1112
|
);
|
|
1083
1113
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1084
1114
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1115
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
1085
1116
|
const pendingState: IPendingContainerState = {
|
|
1086
|
-
pendingRuntimeState
|
|
1117
|
+
pendingRuntimeState,
|
|
1087
1118
|
baseSnapshot: this.baseSnapshot,
|
|
1088
1119
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1089
1120
|
savedOps: this.savedOps,
|
|
@@ -1248,7 +1279,7 @@ export class Container
|
|
|
1248
1279
|
if (!this.closed) {
|
|
1249
1280
|
this.resumeInternal({
|
|
1250
1281
|
fetchOpsFromStorage: false,
|
|
1251
|
-
reason: "createDetached",
|
|
1282
|
+
reason: { text: "createDetached" },
|
|
1252
1283
|
});
|
|
1253
1284
|
}
|
|
1254
1285
|
} catch (error) {
|
|
@@ -1272,7 +1303,7 @@ export class Container
|
|
|
1272
1303
|
);
|
|
1273
1304
|
}
|
|
1274
1305
|
|
|
1275
|
-
private setAutoReconnectInternal(mode: ReconnectMode) {
|
|
1306
|
+
private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
|
|
1276
1307
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
1277
1308
|
|
|
1278
1309
|
if (currentMode === mode) {
|
|
@@ -1291,7 +1322,7 @@ export class Container
|
|
|
1291
1322
|
duration,
|
|
1292
1323
|
});
|
|
1293
1324
|
|
|
1294
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
1325
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
1295
1326
|
}
|
|
1296
1327
|
|
|
1297
1328
|
public connect() {
|
|
@@ -1303,7 +1334,10 @@ export class Container
|
|
|
1303
1334
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
1304
1335
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
1305
1336
|
// assuming that connect() is called quickly after initial container boot.
|
|
1306
|
-
this.connectInternal({
|
|
1337
|
+
this.connectInternal({
|
|
1338
|
+
reason: { text: "DocumentConnect" },
|
|
1339
|
+
fetchOpsFromStorage: false,
|
|
1340
|
+
});
|
|
1307
1341
|
}
|
|
1308
1342
|
}
|
|
1309
1343
|
|
|
@@ -1319,23 +1353,23 @@ export class Container
|
|
|
1319
1353
|
|
|
1320
1354
|
// Set Auto Reconnect Mode
|
|
1321
1355
|
const mode = ReconnectMode.Enabled;
|
|
1322
|
-
this.setAutoReconnectInternal(mode);
|
|
1356
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
1323
1357
|
}
|
|
1324
1358
|
|
|
1325
1359
|
public disconnect() {
|
|
1326
1360
|
if (this.closed) {
|
|
1327
1361
|
throw new UsageError(`The Container is closed and cannot be disconnected`);
|
|
1328
1362
|
} else {
|
|
1329
|
-
this.disconnectInternal();
|
|
1363
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
1330
1364
|
}
|
|
1331
1365
|
}
|
|
1332
1366
|
|
|
1333
|
-
private disconnectInternal() {
|
|
1367
|
+
private disconnectInternal(reason: IConnectionStateChangeReason) {
|
|
1334
1368
|
assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
1335
1369
|
|
|
1336
1370
|
// Set Auto Reconnect Mode
|
|
1337
1371
|
const mode = ReconnectMode.Disabled;
|
|
1338
|
-
this.setAutoReconnectInternal(mode);
|
|
1372
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
1339
1373
|
}
|
|
1340
1374
|
|
|
1341
1375
|
private resumeInternal(args: IConnectionArgs) {
|
|
@@ -1468,7 +1502,8 @@ export class Container
|
|
|
1468
1502
|
specifiedVersion: string | undefined,
|
|
1469
1503
|
loadMode: IContainerLoadMode,
|
|
1470
1504
|
resolvedUrl: IResolvedUrl,
|
|
1471
|
-
pendingLocalState
|
|
1505
|
+
pendingLocalState: IPendingContainerState | undefined,
|
|
1506
|
+
loadToSequenceNumber: number | undefined,
|
|
1472
1507
|
) {
|
|
1473
1508
|
this.service = await this.serviceFactory.createDocumentService(
|
|
1474
1509
|
resolvedUrl,
|
|
@@ -1486,7 +1521,7 @@ export class Container
|
|
|
1486
1521
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
1487
1522
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
1488
1523
|
const connectionArgs: IConnectionArgs = {
|
|
1489
|
-
reason: "DocumentOpen",
|
|
1524
|
+
reason: { text: "DocumentOpen" },
|
|
1490
1525
|
mode: "write",
|
|
1491
1526
|
fetchOpsFromStorage: false,
|
|
1492
1527
|
};
|
|
@@ -1542,6 +1577,57 @@ export class Container
|
|
|
1542
1577
|
|
|
1543
1578
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1544
1579
|
|
|
1580
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
1581
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
1582
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
1583
|
+
assert(
|
|
1584
|
+
loadToSequenceNumber !== undefined,
|
|
1585
|
+
0x727 /* sequenceNumber should be defined */,
|
|
1586
|
+
);
|
|
1587
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
1588
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
1589
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
1590
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
"Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
1598
|
+
this.forceReadonly(true);
|
|
1599
|
+
|
|
1600
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
1601
|
+
const opHandler = () => {
|
|
1602
|
+
if (loadToSequenceNumber === undefined) {
|
|
1603
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
1604
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
} else {
|
|
1608
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
1609
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
1615
|
+
void this.deltaManager.inbound.pause();
|
|
1616
|
+
void this.deltaManager.outbound.pause();
|
|
1617
|
+
this.off("op", opHandler);
|
|
1618
|
+
};
|
|
1619
|
+
if (
|
|
1620
|
+
(loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
1621
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber
|
|
1622
|
+
) {
|
|
1623
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
1624
|
+
opHandler();
|
|
1625
|
+
} else {
|
|
1626
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1627
|
+
this.on("op", opHandler);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1545
1631
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1546
1632
|
// Kick off any ops fetching if required.
|
|
1547
1633
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -1553,6 +1639,9 @@ export class Container
|
|
|
1553
1639
|
loadMode.deltaConnection !== "none" ? "all" : "none",
|
|
1554
1640
|
);
|
|
1555
1641
|
break;
|
|
1642
|
+
case "sequenceNumber":
|
|
1643
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
|
|
1644
|
+
break;
|
|
1556
1645
|
case "cached":
|
|
1557
1646
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
1558
1647
|
break;
|
|
@@ -1636,6 +1725,22 @@ export class Container
|
|
|
1636
1725
|
}
|
|
1637
1726
|
}
|
|
1638
1727
|
|
|
1728
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1729
|
+
if (
|
|
1730
|
+
loadToSequenceNumber !== undefined &&
|
|
1731
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber
|
|
1732
|
+
) {
|
|
1733
|
+
await new Promise<void>((resolve, reject) => {
|
|
1734
|
+
const opHandler = (message: ISequencedDocumentMessage) => {
|
|
1735
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1736
|
+
resolve();
|
|
1737
|
+
this.off("op", opHandler);
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
this.on("op", opHandler);
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1639
1744
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
1640
1745
|
// But if that did not happen for some reason, fail load for sure.
|
|
1641
1746
|
// Otherwise we can get into situations where container is closed and does not try to connect to ordering
|
|
@@ -1919,18 +2024,18 @@ export class Container
|
|
|
1919
2024
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1920
2025
|
});
|
|
1921
2026
|
|
|
1922
|
-
deltaManager.on("establishingConnection", (reason:
|
|
2027
|
+
deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1923
2028
|
this.connectionStateHandler.establishingConnection(reason);
|
|
1924
2029
|
});
|
|
1925
2030
|
|
|
1926
|
-
deltaManager.on("cancelEstablishingConnection", (reason:
|
|
2031
|
+
deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1927
2032
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1928
2033
|
});
|
|
1929
2034
|
|
|
1930
|
-
deltaManager.on("disconnect", (reason:
|
|
2035
|
+
deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
|
|
1931
2036
|
this.noopHeuristic?.notifyDisconnect();
|
|
1932
2037
|
if (!this.closed) {
|
|
1933
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
2038
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1934
2039
|
}
|
|
1935
2040
|
});
|
|
1936
2041
|
|
|
@@ -1965,7 +2070,7 @@ export class Container
|
|
|
1965
2070
|
|
|
1966
2071
|
private async attachDeltaManagerOpHandler(
|
|
1967
2072
|
attributes: IDocumentAttributes,
|
|
1968
|
-
prefetchType?: "cached" | "all" | "none",
|
|
2073
|
+
prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
|
|
1969
2074
|
) {
|
|
1970
2075
|
return this._deltaManager.attachOpHandler(
|
|
1971
2076
|
attributes.minimumSequenceNumber,
|
|
@@ -1983,8 +2088,7 @@ export class Container
|
|
|
1983
2088
|
private logConnectionStateChangeTelemetry(
|
|
1984
2089
|
value: ConnectionState,
|
|
1985
2090
|
oldState: ConnectionState,
|
|
1986
|
-
reason?:
|
|
1987
|
-
error?: IAnyDriverError,
|
|
2091
|
+
reason?: IConnectionStateChangeReason,
|
|
1988
2092
|
) {
|
|
1989
2093
|
// Log actual event
|
|
1990
2094
|
const time = performance.now();
|
|
@@ -2023,7 +2127,7 @@ export class Container
|
|
|
2023
2127
|
from: ConnectionState[oldState],
|
|
2024
2128
|
duration,
|
|
2025
2129
|
durationFromDisconnected,
|
|
2026
|
-
reason,
|
|
2130
|
+
reason: reason?.text,
|
|
2027
2131
|
connectionInitiationReason,
|
|
2028
2132
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
2029
2133
|
clientId: this.clientId,
|
|
@@ -2039,7 +2143,7 @@ export class Container
|
|
|
2039
2143
|
isDirty: this.isDirty,
|
|
2040
2144
|
...this._deltaManager.connectionProps,
|
|
2041
2145
|
},
|
|
2042
|
-
error,
|
|
2146
|
+
reason?.error,
|
|
2043
2147
|
);
|
|
2044
2148
|
|
|
2045
2149
|
if (value === ConnectionState.Connected) {
|
|
@@ -2047,7 +2151,10 @@ export class Container
|
|
|
2047
2151
|
}
|
|
2048
2152
|
}
|
|
2049
2153
|
|
|
2050
|
-
private propagateConnectionState(
|
|
2154
|
+
private propagateConnectionState(
|
|
2155
|
+
initialTransition: boolean,
|
|
2156
|
+
disconnectedReason?: IConnectionStateChangeReason,
|
|
2157
|
+
) {
|
|
2051
2158
|
// When container loaded, we want to propagate initial connection state.
|
|
2052
2159
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
2053
2160
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -2064,7 +2171,7 @@ export class Container
|
|
|
2064
2171
|
|
|
2065
2172
|
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
2066
2173
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
2067
|
-
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
2174
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
2068
2175
|
}
|
|
2069
2176
|
|
|
2070
2177
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -2157,28 +2264,6 @@ export class Container
|
|
|
2157
2264
|
}
|
|
2158
2265
|
const local = this.clientId === message.clientId;
|
|
2159
2266
|
|
|
2160
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
2161
|
-
// flagged should have left, or from a client that's not in the quorum but should be
|
|
2162
|
-
if (message.clientId != null) {
|
|
2163
|
-
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
2164
|
-
|
|
2165
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
2166
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
2167
|
-
throw new Error("Remote message's clientId is missing from the quorum");
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
2171
|
-
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
2172
|
-
// document we don't need to blow up aggressively.
|
|
2173
|
-
if (
|
|
2174
|
-
this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
2175
|
-
!canBeCoalescedByService(message)
|
|
2176
|
-
) {
|
|
2177
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
2178
|
-
throw new Error("Remote message's clientId already should have left");
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
2267
|
// Allow the protocol handler to process the message
|
|
2183
2268
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2184
2269
|
|
|
@@ -2323,9 +2408,7 @@ export class Container
|
|
|
2323
2408
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2324
2409
|
this.updateDirtyContainerState,
|
|
2325
2410
|
this.getAbsoluteUrl,
|
|
2326
|
-
() => this.resolvedUrl?.id,
|
|
2327
2411
|
() => this.clientId,
|
|
2328
|
-
() => this._deltaManager.serviceConfiguration,
|
|
2329
2412
|
() => this.attachState,
|
|
2330
2413
|
() => this.connected,
|
|
2331
2414
|
getSpecifiedCodeDetails,
|
|
@@ -2334,9 +2417,6 @@ export class Container
|
|
|
2334
2417
|
this.subLogger,
|
|
2335
2418
|
pendingLocalState,
|
|
2336
2419
|
);
|
|
2337
|
-
this._lifecycleEvents.once("disposed", () => {
|
|
2338
|
-
context.dispose();
|
|
2339
|
-
});
|
|
2340
2420
|
|
|
2341
2421
|
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2342
2422
|
this.subLogger,
|
|
@@ -2388,7 +2468,7 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2388
2468
|
* @experimental misuse of this API can result in duplicate op submission and potential document corruption
|
|
2389
2469
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2390
2470
|
*/
|
|
2391
|
-
getPendingLocalState?(): string
|
|
2471
|
+
getPendingLocalState?(): Promise<string>;
|
|
2392
2472
|
|
|
2393
2473
|
/**
|
|
2394
2474
|
* Closes the container and returns serialized local state intended to be
|
|
@@ -2396,5 +2476,5 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2396
2476
|
* @experimental
|
|
2397
2477
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2398
2478
|
*/
|
|
2399
|
-
closeAndGetPendingLocalState(): string
|
|
2479
|
+
closeAndGetPendingLocalState?(): Promise<string>;
|
|
2400
2480
|
}
|
package/src/containerContext.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import { FluidObject } from "@fluidframework/core-interfaces";
|
|
19
19
|
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
|
|
20
20
|
import {
|
|
21
|
-
IClientConfiguration,
|
|
22
21
|
IClientDetails,
|
|
23
22
|
IDocumentMessage,
|
|
24
23
|
IQuorumClients,
|
|
@@ -46,13 +45,6 @@ export class ContainerContext implements IContainerContext {
|
|
|
46
45
|
return this._getClientId();
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
/**
|
|
50
|
-
* DISCLAIMER: this id is only for telemetry purposes. Not suitable for any other usages.
|
|
51
|
-
*/
|
|
52
|
-
public get id(): string {
|
|
53
|
-
return this._getContainerDiagnosticId() ?? "";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
48
|
/**
|
|
57
49
|
* When true, ops are free to flow
|
|
58
50
|
* When false, ops should be kept as pending or rejected
|
|
@@ -61,16 +53,6 @@ export class ContainerContext implements IContainerContext {
|
|
|
61
53
|
return this._getConnected();
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
public get serviceConfiguration(): IClientConfiguration | undefined {
|
|
65
|
-
return this._getServiceConfiguration();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private _disposed = false;
|
|
69
|
-
|
|
70
|
-
public get disposed() {
|
|
71
|
-
return this._disposed;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
56
|
constructor(
|
|
75
57
|
public readonly options: ILoaderOptions,
|
|
76
58
|
public readonly scope: FluidObject,
|
|
@@ -101,9 +83,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
101
83
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
102
84
|
public readonly updateDirtyContainerState: (dirty: boolean) => void,
|
|
103
85
|
public readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>,
|
|
104
|
-
private readonly _getContainerDiagnosticId: () => string | undefined,
|
|
105
86
|
private readonly _getClientId: () => string | undefined,
|
|
106
|
-
private readonly _getServiceConfiguration: () => IClientConfiguration | undefined,
|
|
107
87
|
private readonly _getAttachState: () => AttachState,
|
|
108
88
|
private readonly _getConnected: () => boolean,
|
|
109
89
|
public readonly getSpecifiedCodeDetails: () => IFluidCodeDetails | undefined,
|
|
@@ -113,10 +93,6 @@ export class ContainerContext implements IContainerContext {
|
|
|
113
93
|
public readonly pendingLocalState?: unknown,
|
|
114
94
|
) {}
|
|
115
95
|
|
|
116
|
-
public dispose(error?: Error): void {
|
|
117
|
-
this._disposed = true;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
96
|
public getLoadedFromVersion(): IVersion | undefined {
|
|
121
97
|
return this._version;
|
|
122
98
|
}
|
package/src/contracts.ts
CHANGED
|
@@ -5,22 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
import { ITelemetryProperties } from "@fluidframework/core-interfaces";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
ReadOnlyInfo,
|
|
10
|
-
IConnectionDetailsInternal,
|
|
8
|
+
IConnectionDetails,
|
|
11
9
|
ICriticalContainerError,
|
|
10
|
+
IDeltaQueue,
|
|
11
|
+
IErrorBase,
|
|
12
12
|
IFluidCodeDetails,
|
|
13
13
|
isFluidPackage,
|
|
14
|
+
ReadOnlyInfo,
|
|
14
15
|
} from "@fluidframework/container-definitions";
|
|
15
16
|
import {
|
|
16
17
|
ConnectionMode,
|
|
17
|
-
IDocumentMessage,
|
|
18
|
-
ISequencedDocumentMessage,
|
|
19
18
|
IClientConfiguration,
|
|
20
19
|
IClientDetails,
|
|
20
|
+
IDocumentMessage,
|
|
21
|
+
ISequencedDocumentMessage,
|
|
22
|
+
ISignalClient,
|
|
21
23
|
ISignalMessage,
|
|
22
24
|
} from "@fluidframework/protocol-definitions";
|
|
23
|
-
import {
|
|
25
|
+
import { IContainerPackageInfo } from "@fluidframework/driver-definitions";
|
|
24
26
|
|
|
25
27
|
export enum ReconnectMode {
|
|
26
28
|
Never = "Never",
|
|
@@ -28,6 +30,21 @@ export enum ReconnectMode {
|
|
|
28
30
|
Enabled = "Enabled",
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
export interface IConnectionStateChangeReason<T extends IErrorBase = IErrorBase> {
|
|
34
|
+
text: string;
|
|
35
|
+
error?: T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Internal version of IConnectionDetails with props are only exposed internally
|
|
40
|
+
*/
|
|
41
|
+
export interface IConnectionDetailsInternal extends IConnectionDetails {
|
|
42
|
+
mode: ConnectionMode;
|
|
43
|
+
version: string;
|
|
44
|
+
initialClients: ISignalClient[];
|
|
45
|
+
reason: IConnectionStateChangeReason;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
/**
|
|
32
49
|
* Connection manager (implements this interface) is responsible for maintaining connection
|
|
33
50
|
* to relay service.
|
|
@@ -95,7 +112,7 @@ export interface IConnectionManager {
|
|
|
95
112
|
/**
|
|
96
113
|
* Initiates connection to relay service (noop if already connected).
|
|
97
114
|
*/
|
|
98
|
-
connect(reason:
|
|
115
|
+
connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode): void;
|
|
99
116
|
|
|
100
117
|
/**
|
|
101
118
|
* Disposed connection manager
|
|
@@ -139,7 +156,7 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
139
156
|
/**
|
|
140
157
|
* Called whenever connection to relay service is lost.
|
|
141
158
|
*/
|
|
142
|
-
readonly disconnectHandler: (reason:
|
|
159
|
+
readonly disconnectHandler: (reason: IConnectionStateChangeReason) => void;
|
|
143
160
|
|
|
144
161
|
/**
|
|
145
162
|
* Called whenever new connection to rely service is established
|
|
@@ -169,12 +186,12 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
169
186
|
/**
|
|
170
187
|
* Called whenever we try to start establishing a new connection.
|
|
171
188
|
*/
|
|
172
|
-
readonly establishConnectionHandler: (reason:
|
|
189
|
+
readonly establishConnectionHandler: (reason: IConnectionStateChangeReason) => void;
|
|
173
190
|
|
|
174
191
|
/**
|
|
175
192
|
* Called whenever we cancel the connection in progress.
|
|
176
193
|
*/
|
|
177
|
-
readonly cancelConnectionHandler: (reason:
|
|
194
|
+
readonly cancelConnectionHandler: (reason: IConnectionStateChangeReason) => void;
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
/**
|