@fluidframework/container-loader 2.0.0-internal.1.1.2 → 2.0.0-internal.1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catchUpMonitor.d.ts +6 -17
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +5 -36
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js +2 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +8 -11
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +80 -26
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +170 -89
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +22 -11
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +130 -142
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +18 -8
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +10 -24
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +50 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.d.ts +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +18 -6
- package/dist/deltaManager.js.map +1 -1
- package/dist/loader.d.ts +1 -1
- 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 +2 -1
- package/dist/protocol.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +6 -17
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +5 -35
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js +3 -2
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +9 -14
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +80 -26
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +170 -90
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +22 -11
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +133 -145
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +19 -9
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +10 -24
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +50 -15
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.d.ts +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +18 -6
- package/lib/deltaManager.js.map +1 -1
- package/lib/loader.d.ts +1 -1
- 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 +2 -1
- package/lib/protocol.js.map +1 -1
- package/package.json +13 -13
- package/src/catchUpMonitor.ts +7 -47
- package/src/collabWindowTracker.ts +4 -3
- package/src/connectionManager.ts +9 -11
- package/src/connectionStateHandler.ts +231 -106
- package/src/container.ts +156 -168
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +64 -15
- package/src/deltaManager.ts +20 -7
- package/src/loader.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +2 -1
package/src/container.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
ITelemetryLogger, ITelemetryProperties,
|
|
11
11
|
} from "@fluidframework/common-definitions";
|
|
12
12
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
13
13
|
import {
|
|
@@ -29,10 +29,9 @@ import {
|
|
|
29
29
|
IContainerLoadMode,
|
|
30
30
|
IFluidCodeDetails,
|
|
31
31
|
isFluidCodeDetails,
|
|
32
|
+
IBatchMessage,
|
|
32
33
|
} from "@fluidframework/container-definitions";
|
|
33
34
|
import {
|
|
34
|
-
DataCorruptionError,
|
|
35
|
-
extractSafePropertiesFromMessage,
|
|
36
35
|
GenericError,
|
|
37
36
|
UsageError,
|
|
38
37
|
} from "@fluidframework/container-utils";
|
|
@@ -50,8 +49,6 @@ import {
|
|
|
50
49
|
combineAppAndProtocolSummary,
|
|
51
50
|
runWithRetry,
|
|
52
51
|
isFluidResolvedUrl,
|
|
53
|
-
isRuntimeMessage,
|
|
54
|
-
isUnpackedRuntimeMessage,
|
|
55
52
|
} from "@fluidframework/driver-utils";
|
|
56
53
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
57
54
|
import {
|
|
@@ -61,7 +58,6 @@ import {
|
|
|
61
58
|
ICommittedProposal,
|
|
62
59
|
IDocumentAttributes,
|
|
63
60
|
IDocumentMessage,
|
|
64
|
-
IProcessMessageResult,
|
|
65
61
|
IProtocolState,
|
|
66
62
|
IQuorumClients,
|
|
67
63
|
IQuorumProposals,
|
|
@@ -97,10 +93,11 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
97
93
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
98
94
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
99
95
|
import { pkgVersion } from "./packageVersion";
|
|
100
|
-
import {
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
97
|
+
import {
|
|
98
|
+
IConnectionStateHandler,
|
|
99
|
+
createConnectionStateHandler,
|
|
100
|
+
} from "./connectionStateHandler";
|
|
104
101
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
105
102
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
106
103
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
@@ -151,14 +148,19 @@ export interface IContainerConfig {
|
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
/**
|
|
154
|
-
* Waits until container connects to delta storage and gets up-to-date
|
|
151
|
+
* Waits until container connects to delta storage and gets up-to-date.
|
|
152
|
+
*
|
|
155
153
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
156
154
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
155
|
+
*
|
|
157
156
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
158
157
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
158
|
+
*
|
|
159
|
+
* @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
|
|
160
|
+
*
|
|
161
|
+
* `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
|
|
162
|
+
* but it maybe still behind.
|
|
163
|
+
*
|
|
162
164
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
163
165
|
*/
|
|
164
166
|
export async function waitContainerToCatchUp(container: IContainer) {
|
|
@@ -391,7 +393,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
391
393
|
// Only transition states if currently loading
|
|
392
394
|
if (this._lifecycleState === "loading") {
|
|
393
395
|
// Propagate current connection state through the system.
|
|
394
|
-
this.propagateConnectionState();
|
|
396
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
395
397
|
this._lifecycleState = "loaded";
|
|
396
398
|
}
|
|
397
399
|
}
|
|
@@ -402,17 +404,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
402
404
|
|
|
403
405
|
private _attachState = AttachState.Detached;
|
|
404
406
|
|
|
405
|
-
private readonly
|
|
407
|
+
private readonly storageService: ContainerStorageAdapter;
|
|
406
408
|
public get storage(): IDocumentStorageService {
|
|
407
|
-
return this.
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private _storageService: IDocumentStorageService & IDisposable | undefined;
|
|
411
|
-
private get storageService(): IDocumentStorageService {
|
|
412
|
-
if (this._storageService === undefined) {
|
|
413
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
414
|
-
}
|
|
415
|
-
return this._storageService;
|
|
409
|
+
return this.storageService;
|
|
416
410
|
}
|
|
417
411
|
|
|
418
412
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
@@ -447,7 +441,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
447
441
|
|
|
448
442
|
private lastVisible: number | undefined;
|
|
449
443
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
450
|
-
private readonly connectionStateHandler:
|
|
444
|
+
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
451
445
|
|
|
452
446
|
private setAutoReconnectTime = performance.now();
|
|
453
447
|
|
|
@@ -489,7 +483,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
489
483
|
}
|
|
490
484
|
|
|
491
485
|
public get connected(): boolean {
|
|
492
|
-
return this.connectionStateHandler.
|
|
486
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
493
487
|
}
|
|
494
488
|
|
|
495
489
|
/**
|
|
@@ -500,12 +494,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
500
494
|
return this._deltaManager.serviceConfiguration;
|
|
501
495
|
}
|
|
502
496
|
|
|
497
|
+
private _clientId: string | undefined;
|
|
498
|
+
|
|
503
499
|
/**
|
|
504
500
|
* The server provided id of the client.
|
|
505
501
|
* Set once this.connected is true, otherwise undefined
|
|
506
502
|
*/
|
|
507
503
|
public get clientId(): string | undefined {
|
|
508
|
-
return this.
|
|
504
|
+
return this._clientId;
|
|
509
505
|
}
|
|
510
506
|
|
|
511
507
|
/**
|
|
@@ -631,11 +627,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
631
627
|
summarizeProtocolTree,
|
|
632
628
|
};
|
|
633
629
|
|
|
634
|
-
this.
|
|
630
|
+
this._deltaManager = this.createDeltaManager();
|
|
631
|
+
|
|
632
|
+
this._clientId = config.serializedContainerState?.clientId;
|
|
633
|
+
this.connectionStateHandler = createConnectionStateHandler(
|
|
635
634
|
{
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
635
|
+
logger: this.mc.logger,
|
|
636
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
637
|
+
if (value === ConnectionState.Connected) {
|
|
638
|
+
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
639
|
+
}
|
|
640
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
641
|
+
if (this._lifecycleState === "loaded") {
|
|
642
|
+
this.propagateConnectionState(false /* initial transition */);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
639
645
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
640
646
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
641
647
|
logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
|
|
@@ -647,35 +653,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
647
653
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
648
654
|
});
|
|
649
655
|
},
|
|
650
|
-
connectionStateChanged: () => {
|
|
651
|
-
// Fire events only if container is fully loaded and not closed
|
|
652
|
-
if (this._lifecycleState === "loaded") {
|
|
653
|
-
this.propagateConnectionState();
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
656
|
},
|
|
657
|
-
this.
|
|
658
|
-
|
|
657
|
+
this.deltaManager,
|
|
658
|
+
this._clientId,
|
|
659
659
|
);
|
|
660
660
|
|
|
661
661
|
this.on(savedContainerEvent, () => {
|
|
662
662
|
this.connectionStateHandler.containerSaved();
|
|
663
663
|
});
|
|
664
664
|
|
|
665
|
-
this.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
this.mc.logger.sendErrorEvent({
|
|
673
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
674
|
-
});
|
|
675
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
676
|
-
}
|
|
677
|
-
return this.storageService;
|
|
678
|
-
},
|
|
665
|
+
this.storageService = new ContainerStorageAdapter(
|
|
666
|
+
this.loader.services.detachedBlobStorage,
|
|
667
|
+
this.mc.logger,
|
|
668
|
+
this.options.summarizeProtocolTree === true
|
|
669
|
+
? () => this.captureProtocolSummary()
|
|
670
|
+
: undefined,
|
|
679
671
|
);
|
|
680
672
|
|
|
681
673
|
const isDomAvailable = typeof document === "object" &&
|
|
@@ -775,7 +767,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
775
767
|
|
|
776
768
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
777
769
|
|
|
778
|
-
this.
|
|
770
|
+
this.storageService.dispose();
|
|
779
771
|
|
|
780
772
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
781
773
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -897,7 +889,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
897
889
|
const resolvedUrl = this.service.resolvedUrl;
|
|
898
890
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
899
891
|
this._resolvedUrl = resolvedUrl;
|
|
900
|
-
await this.
|
|
892
|
+
await this.storageService.connectToService(this.service);
|
|
901
893
|
|
|
902
894
|
if (hasAttachmentBlobs) {
|
|
903
895
|
// upload blobs to storage
|
|
@@ -935,8 +927,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
935
927
|
this._attachState = AttachState.Attached;
|
|
936
928
|
this.emit("attached");
|
|
937
929
|
|
|
938
|
-
// Propagate current connection state through the system.
|
|
939
|
-
this.propagateConnectionState();
|
|
940
930
|
if (!this.closed) {
|
|
941
931
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
942
932
|
}
|
|
@@ -1112,9 +1102,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1112
1102
|
/**
|
|
1113
1103
|
* Load container.
|
|
1114
1104
|
*
|
|
1115
|
-
* @param specifiedVersion -
|
|
1116
|
-
* - undefined - fetch latest snapshot
|
|
1117
|
-
* - otherwise, version sha to load snapshot
|
|
1105
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
1118
1106
|
*/
|
|
1119
1107
|
private async load(
|
|
1120
1108
|
specifiedVersion: string | undefined,
|
|
@@ -1148,10 +1136,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1148
1136
|
}
|
|
1149
1137
|
|
|
1150
1138
|
if (!pendingLocalState) {
|
|
1151
|
-
await this.
|
|
1139
|
+
await this.storageService.connectToService(this.service);
|
|
1152
1140
|
} else {
|
|
1153
1141
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1154
|
-
this.
|
|
1142
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
1155
1143
|
}
|
|
1156
1144
|
|
|
1157
1145
|
this._attachState = AttachState.Attached;
|
|
@@ -1192,12 +1180,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1192
1180
|
|
|
1193
1181
|
// ...load in the existing quorum
|
|
1194
1182
|
// Initialize the protocol handler
|
|
1195
|
-
|
|
1196
|
-
|
|
1183
|
+
if (pendingLocalState === undefined) {
|
|
1184
|
+
await this.initializeProtocolStateFromSnapshot(
|
|
1197
1185
|
attributes,
|
|
1198
1186
|
this.storageService,
|
|
1199
|
-
snapshot
|
|
1200
|
-
|
|
1187
|
+
snapshot);
|
|
1188
|
+
} else {
|
|
1189
|
+
this.initializeProtocolState(
|
|
1201
1190
|
attributes,
|
|
1202
1191
|
{
|
|
1203
1192
|
members: pendingLocalState.protocol.members,
|
|
@@ -1205,6 +1194,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1205
1194
|
values: pendingLocalState.protocol.values,
|
|
1206
1195
|
}, // pending IQuorumSnapshot
|
|
1207
1196
|
);
|
|
1197
|
+
}
|
|
1208
1198
|
|
|
1209
1199
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1210
1200
|
await this.instantiateContext(
|
|
@@ -1279,7 +1269,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1279
1269
|
|
|
1280
1270
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1281
1271
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
1282
|
-
this.
|
|
1272
|
+
this.initializeProtocolState(
|
|
1283
1273
|
attributes,
|
|
1284
1274
|
{
|
|
1285
1275
|
members: [],
|
|
@@ -1304,27 +1294,26 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1304
1294
|
}
|
|
1305
1295
|
|
|
1306
1296
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1307
|
-
this.
|
|
1308
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1297
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1298
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
1309
1299
|
|
|
1310
1300
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1311
1301
|
|
|
1312
1302
|
// Initialize the protocol handler
|
|
1313
1303
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1314
1304
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1315
|
-
this.
|
|
1305
|
+
this.storageService,
|
|
1316
1306
|
baseTree.blobs.quorumValues,
|
|
1317
1307
|
);
|
|
1318
1308
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1319
|
-
this.
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
);
|
|
1309
|
+
this.initializeProtocolState(
|
|
1310
|
+
attributes,
|
|
1311
|
+
{
|
|
1312
|
+
members: [],
|
|
1313
|
+
proposals: [],
|
|
1314
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1315
|
+
}, // IQuorumSnapShot
|
|
1316
|
+
);
|
|
1328
1317
|
|
|
1329
1318
|
await this.instantiateContextDetached(
|
|
1330
1319
|
true, // existing
|
|
@@ -1334,28 +1323,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1334
1323
|
this.setLoaded();
|
|
1335
1324
|
}
|
|
1336
1325
|
|
|
1337
|
-
private async connectStorageService(): Promise<void> {
|
|
1338
|
-
if (this._storageService !== undefined) {
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
1343
|
-
const storageService = await this.service.connectToStorage();
|
|
1344
|
-
|
|
1345
|
-
this._storageService =
|
|
1346
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
1347
|
-
|
|
1348
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
1349
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
1350
|
-
this._storageService =
|
|
1351
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
1355
|
-
assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
|
|
1356
|
-
0x0e0 /* "lost minBlobSize policy" */);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
1326
|
private async getDocumentAttributes(
|
|
1360
1327
|
storage: IDocumentStorageService,
|
|
1361
1328
|
tree: ISnapshotTree | undefined,
|
|
@@ -1387,7 +1354,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1387
1354
|
attributes: IDocumentAttributes,
|
|
1388
1355
|
storage: IDocumentStorageService,
|
|
1389
1356
|
snapshot: ISnapshotTree | undefined,
|
|
1390
|
-
): Promise<
|
|
1357
|
+
): Promise<void> {
|
|
1391
1358
|
const quorumSnapshot: IQuorumSnapshot = {
|
|
1392
1359
|
members: [],
|
|
1393
1360
|
proposals: [],
|
|
@@ -1403,20 +1370,19 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1403
1370
|
]);
|
|
1404
1371
|
}
|
|
1405
1372
|
|
|
1406
|
-
|
|
1407
|
-
return protocolHandler;
|
|
1373
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1408
1374
|
}
|
|
1409
1375
|
|
|
1410
|
-
private
|
|
1376
|
+
private initializeProtocolState(
|
|
1411
1377
|
attributes: IDocumentAttributes,
|
|
1412
1378
|
quorumSnapshot: IQuorumSnapshot,
|
|
1413
|
-
):
|
|
1379
|
+
): void {
|
|
1414
1380
|
const protocolHandlerBuilder =
|
|
1415
1381
|
this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1416
1382
|
const protocol = protocolHandlerBuilder(
|
|
1417
1383
|
attributes,
|
|
1418
1384
|
quorumSnapshot,
|
|
1419
|
-
(key, value) => this.submitMessage(MessageType.Propose, { key, value }),
|
|
1385
|
+
(key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1420
1386
|
this._initialClients ?? [],
|
|
1421
1387
|
);
|
|
1422
1388
|
|
|
@@ -1452,8 +1418,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1452
1418
|
});
|
|
1453
1419
|
}
|
|
1454
1420
|
});
|
|
1455
|
-
|
|
1456
|
-
|
|
1421
|
+
// we need to make sure this member get set in a synchronous context,
|
|
1422
|
+
// or other things can happen after the object that will be set is created, but not yet set
|
|
1423
|
+
// this was breaking this._initialClients handling
|
|
1424
|
+
//
|
|
1425
|
+
this._protocolHandler = protocol;
|
|
1457
1426
|
}
|
|
1458
1427
|
|
|
1459
1428
|
private captureProtocolSummary(): ISummaryTree {
|
|
@@ -1557,15 +1526,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1557
1526
|
}
|
|
1558
1527
|
}
|
|
1559
1528
|
|
|
1560
|
-
const deltaManagerForCatchingUp =
|
|
1561
|
-
this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
|
|
1562
|
-
this.deltaManager
|
|
1563
|
-
: undefined;
|
|
1564
|
-
|
|
1565
1529
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1566
1530
|
this.connectionMode,
|
|
1567
1531
|
details,
|
|
1568
|
-
deltaManagerForCatchingUp,
|
|
1569
1532
|
);
|
|
1570
1533
|
});
|
|
1571
1534
|
|
|
@@ -1585,6 +1548,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1585
1548
|
});
|
|
1586
1549
|
|
|
1587
1550
|
deltaManager.on("readonly", (readonly) => {
|
|
1551
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1588
1552
|
this.emit("readonly", readonly);
|
|
1589
1553
|
});
|
|
1590
1554
|
|
|
@@ -1639,11 +1603,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1639
1603
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1640
1604
|
}
|
|
1641
1605
|
}
|
|
1642
|
-
|
|
1643
|
-
connectionInitiationReason = "InitialConnect";
|
|
1644
|
-
} else {
|
|
1645
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1646
|
-
}
|
|
1606
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1647
1607
|
}
|
|
1648
1608
|
|
|
1649
1609
|
this.mc.logger.sendPerformanceEvent({
|
|
@@ -1669,7 +1629,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1669
1629
|
}
|
|
1670
1630
|
}
|
|
1671
1631
|
|
|
1672
|
-
private propagateConnectionState() {
|
|
1632
|
+
private propagateConnectionState(initialTransition: boolean) {
|
|
1633
|
+
// When container loaded, we want to propagate initial connection state.
|
|
1634
|
+
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1635
|
+
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1636
|
+
if (!initialTransition &&
|
|
1637
|
+
this.connectionState !== ConnectionState.Connected &&
|
|
1638
|
+
this.connectionState !== ConnectionState.Disconnected) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const state = this.connectionState === ConnectionState.Connected;
|
|
1642
|
+
|
|
1673
1643
|
const logOpsOnReconnect: boolean =
|
|
1674
1644
|
this.connectionState === ConnectionState.Connected &&
|
|
1675
1645
|
!this.firstConnection &&
|
|
@@ -1678,13 +1648,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1678
1648
|
this.messageCountAfterDisconnection = 0;
|
|
1679
1649
|
}
|
|
1680
1650
|
|
|
1681
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1682
|
-
|
|
1683
1651
|
// Both protocol and context should not be undefined if we got so far.
|
|
1684
1652
|
|
|
1685
|
-
|
|
1686
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1687
|
-
}
|
|
1653
|
+
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
1688
1654
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1689
1655
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1690
1656
|
|
|
@@ -1694,35 +1660,53 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1694
1660
|
}
|
|
1695
1661
|
}
|
|
1696
1662
|
|
|
1663
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1697
1664
|
private submitContainerMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
|
|
1698
|
-
|
|
1699
|
-
switch (outboundMessageType) {
|
|
1665
|
+
switch (type) {
|
|
1700
1666
|
case MessageType.Operation:
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
const summary = contents as ISummaryContent;
|
|
1709
|
-
if (summary.details === undefined) {
|
|
1710
|
-
summary.details = {};
|
|
1711
|
-
}
|
|
1712
|
-
summary.details.includesProtocolTree =
|
|
1713
|
-
this.options.summarizeProtocolTree === true;
|
|
1714
|
-
break;
|
|
1715
|
-
}
|
|
1667
|
+
return this.submitMessage(
|
|
1668
|
+
type,
|
|
1669
|
+
JSON.stringify(contents),
|
|
1670
|
+
batch,
|
|
1671
|
+
metadata);
|
|
1672
|
+
case MessageType.Summarize:
|
|
1673
|
+
return this.submitSummaryMessage(contents as unknown as ISummaryContent);
|
|
1716
1674
|
default:
|
|
1717
1675
|
this.close(new GenericError("invalidContainerSubmitOpType",
|
|
1718
1676
|
undefined /* error */,
|
|
1719
1677
|
{ messageType: type }));
|
|
1720
1678
|
return -1;
|
|
1721
1679
|
}
|
|
1722
|
-
return this.submitMessage(type, contents, batch, metadata);
|
|
1723
1680
|
}
|
|
1724
1681
|
|
|
1725
|
-
|
|
1682
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
1683
|
+
private submitBatch(batch: IBatchMessage[]): number {
|
|
1684
|
+
let clientSequenceNumber = -1;
|
|
1685
|
+
for (const message of batch) {
|
|
1686
|
+
clientSequenceNumber = this.submitMessage(
|
|
1687
|
+
MessageType.Operation,
|
|
1688
|
+
message.contents,
|
|
1689
|
+
true, // batch
|
|
1690
|
+
message.metadata);
|
|
1691
|
+
}
|
|
1692
|
+
this._deltaManager.flush();
|
|
1693
|
+
return clientSequenceNumber;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
private submitSummaryMessage(summary: ISummaryContent) {
|
|
1697
|
+
// github #6451: this is only needed for staging so the server
|
|
1698
|
+
// know when the protocol tree is included
|
|
1699
|
+
// this can be removed once all clients send
|
|
1700
|
+
// protocol tree by default
|
|
1701
|
+
if (summary.details === undefined) {
|
|
1702
|
+
summary.details = {};
|
|
1703
|
+
}
|
|
1704
|
+
summary.details.includesProtocolTree =
|
|
1705
|
+
this.options.summarizeProtocolTree === true;
|
|
1706
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
private submitMessage(type: MessageType, contents?: string, batch?: boolean, metadata?: any): number {
|
|
1726
1710
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1727
1711
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1728
1712
|
return -1;
|
|
@@ -1733,28 +1717,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1733
1717
|
return this._deltaManager.submit(type, contents, batch, metadata);
|
|
1734
1718
|
}
|
|
1735
1719
|
|
|
1736
|
-
private processRemoteMessage(message: ISequencedDocumentMessage)
|
|
1720
|
+
private processRemoteMessage(message: ISequencedDocumentMessage) {
|
|
1737
1721
|
const local = this.clientId === message.clientId;
|
|
1738
1722
|
|
|
1739
1723
|
// Allow the protocol handler to process the message
|
|
1740
|
-
|
|
1741
|
-
try {
|
|
1742
|
-
result = this.protocolHandler.processMessage(message, local);
|
|
1743
|
-
} catch (error) {
|
|
1744
|
-
this.close(wrapError(error, (errorMessage) =>
|
|
1745
|
-
new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1746
|
-
}
|
|
1724
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1747
1725
|
|
|
1748
|
-
//
|
|
1749
|
-
|
|
1750
|
-
this.mc.logger.sendTelemetryEvent(
|
|
1751
|
-
{ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1752
|
-
}
|
|
1753
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1754
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1755
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1756
|
-
this.context.process(message, local, undefined);
|
|
1757
|
-
}
|
|
1726
|
+
// Forward messages to the loaded runtime for processing
|
|
1727
|
+
this.context.process(message, local, undefined);
|
|
1758
1728
|
|
|
1759
1729
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1760
1730
|
if (this.activeConnection()) {
|
|
@@ -1767,10 +1737,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1767
1737
|
this.serviceConfiguration !== undefined,
|
|
1768
1738
|
0x2e4 /* "there should be service config for active connection" */);
|
|
1769
1739
|
this.collabWindowTracker = new CollabWindowTracker(
|
|
1770
|
-
(type
|
|
1740
|
+
(type) => {
|
|
1771
1741
|
assert(this.activeConnection(),
|
|
1772
1742
|
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1773
|
-
this.submitMessage(type
|
|
1743
|
+
this.submitMessage(type);
|
|
1774
1744
|
},
|
|
1775
1745
|
this.serviceConfiguration.noopTimeFrequency,
|
|
1776
1746
|
this.serviceConfiguration.noopCountFrequency,
|
|
@@ -1780,8 +1750,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1780
1750
|
}
|
|
1781
1751
|
|
|
1782
1752
|
this.emit("op", message);
|
|
1783
|
-
|
|
1784
|
-
return result;
|
|
1785
1753
|
}
|
|
1786
1754
|
|
|
1787
1755
|
private submitSignal(message: any) {
|
|
@@ -1857,6 +1825,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1857
1825
|
new QuorumProxy(this.protocolHandler.quorum),
|
|
1858
1826
|
loader,
|
|
1859
1827
|
(type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata),
|
|
1828
|
+
(summaryOp: ISummaryContent) => this.submitSummaryMessage(summaryOp),
|
|
1829
|
+
(batch: IBatchMessage[]) => this.submitBatch(batch),
|
|
1860
1830
|
(message) => this.submitSignal(message),
|
|
1861
1831
|
(error?: ICriticalContainerError) => this.close(error),
|
|
1862
1832
|
Container.version,
|
|
@@ -1879,4 +1849,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1879
1849
|
private logContainerError(warning: ContainerWarning) {
|
|
1880
1850
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1881
1851
|
}
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* Set the connected state of the ContainerContext
|
|
1855
|
+
* This controls the "connected" state of the ContainerRuntime as well
|
|
1856
|
+
* @param state - Is the container currently connected?
|
|
1857
|
+
* @param readonly - Is the container in readonly mode?
|
|
1858
|
+
*/
|
|
1859
|
+
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
1860
|
+
if (this._context?.disposed === false) {
|
|
1861
|
+
/**
|
|
1862
|
+
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1863
|
+
* ops getting through to the DeltaManager.
|
|
1864
|
+
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1865
|
+
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1866
|
+
*/
|
|
1867
|
+
this.context.setConnectionState(state && !readonly, this.clientId);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1882
1870
|
}
|