@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/lib/container.js
CHANGED
|
@@ -7,8 +7,8 @@ import merge from "lodash/merge";
|
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
8
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
|
-
import {
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl,
|
|
10
|
+
import { GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
13
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
14
14
|
import { Audience } from "./audience";
|
|
@@ -18,10 +18,8 @@ import { DeltaManager } from "./deltaManager";
|
|
|
18
18
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
19
19
|
import { RelativeLoader } from "./loader";
|
|
20
20
|
import { pkgVersion } from "./packageVersion";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
24
|
-
import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
21
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
22
|
+
import { createConnectionStateHandler, } from "./connectionStateHandler";
|
|
25
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
26
24
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
27
25
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
@@ -32,14 +30,19 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
32
30
|
const dirtyContainerEvent = "dirty";
|
|
33
31
|
const savedContainerEvent = "saved";
|
|
34
32
|
/**
|
|
35
|
-
* Waits until container connects to delta storage and gets up-to-date
|
|
33
|
+
* Waits until container connects to delta storage and gets up-to-date.
|
|
34
|
+
*
|
|
36
35
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
37
36
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
37
|
+
*
|
|
38
38
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
39
39
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
*
|
|
41
|
+
* @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
|
|
42
|
+
*
|
|
43
|
+
* `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
|
|
44
|
+
* but it maybe still behind.
|
|
45
|
+
*
|
|
43
46
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
44
47
|
*/
|
|
45
48
|
export async function waitContainerToCatchUp(container) {
|
|
@@ -183,9 +186,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
183
186
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
184
187
|
const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
|
|
185
188
|
this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
|
|
186
|
-
this.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
this._deltaManager = this.createDeltaManager();
|
|
190
|
+
this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
|
|
191
|
+
this.connectionStateHandler = createConnectionStateHandler({
|
|
192
|
+
logger: this.mc.logger,
|
|
193
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
194
|
+
if (value === ConnectionState.Connected) {
|
|
195
|
+
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
196
|
+
}
|
|
197
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
198
|
+
if (this._lifecycleState === "loaded") {
|
|
199
|
+
this.propagateConnectionState(false /* initial transition */);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
189
202
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
190
203
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
191
204
|
logConnectionIssue: (eventName, details) => {
|
|
@@ -193,29 +206,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
193
206
|
// its own join op. Attempt recovery option.
|
|
194
207
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
195
208
|
},
|
|
196
|
-
|
|
197
|
-
// Fire events only if container is fully loaded and not closed
|
|
198
|
-
if (this._lifecycleState === "loaded") {
|
|
199
|
-
this.propagateConnectionState();
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
}, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
|
|
209
|
+
}, this.deltaManager, this._clientId);
|
|
203
210
|
this.on(savedContainerEvent, () => {
|
|
204
211
|
this.connectionStateHandler.containerSaved();
|
|
205
212
|
});
|
|
206
|
-
this.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (this.loader.services.detachedBlobStorage !== undefined) {
|
|
210
|
-
return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
|
|
211
|
-
}
|
|
212
|
-
this.mc.logger.sendErrorEvent({
|
|
213
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
214
|
-
});
|
|
215
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
216
|
-
}
|
|
217
|
-
return this.storageService;
|
|
218
|
-
});
|
|
213
|
+
this.storageService = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
|
|
214
|
+
? () => this.captureProtocolSummary()
|
|
215
|
+
: undefined);
|
|
219
216
|
const isDomAvailable = typeof document === "object" &&
|
|
220
217
|
document !== null &&
|
|
221
218
|
typeof document.addEventListener === "function" &&
|
|
@@ -336,7 +333,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
336
333
|
// Only transition states if currently loading
|
|
337
334
|
if (this._lifecycleState === "loading") {
|
|
338
335
|
// Propagate current connection state through the system.
|
|
339
|
-
this.propagateConnectionState();
|
|
336
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
340
337
|
this._lifecycleState = "loaded";
|
|
341
338
|
}
|
|
342
339
|
}
|
|
@@ -344,13 +341,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
344
341
|
return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
|
|
345
342
|
}
|
|
346
343
|
get storage() {
|
|
347
|
-
return this.
|
|
348
|
-
}
|
|
349
|
-
get storageService() {
|
|
350
|
-
if (this._storageService === undefined) {
|
|
351
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
352
|
-
}
|
|
353
|
-
return this._storageService;
|
|
344
|
+
return this.storageService;
|
|
354
345
|
}
|
|
355
346
|
get context() {
|
|
356
347
|
if (this._context === undefined) {
|
|
@@ -391,7 +382,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
391
382
|
return this.connectionStateHandler.connectionState;
|
|
392
383
|
}
|
|
393
384
|
get connected() {
|
|
394
|
-
return this.connectionStateHandler.
|
|
385
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
395
386
|
}
|
|
396
387
|
/**
|
|
397
388
|
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
@@ -405,7 +396,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
405
396
|
* Set once this.connected is true, otherwise undefined
|
|
406
397
|
*/
|
|
407
398
|
get clientId() {
|
|
408
|
-
return this.
|
|
399
|
+
return this._clientId;
|
|
409
400
|
}
|
|
410
401
|
/**
|
|
411
402
|
* The server provided claims of the client.
|
|
@@ -467,7 +458,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
467
458
|
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
468
459
|
}
|
|
469
460
|
closeCore(error) {
|
|
470
|
-
var _a, _b, _c
|
|
461
|
+
var _a, _b, _c;
|
|
471
462
|
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
472
463
|
try {
|
|
473
464
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -482,11 +473,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
482
473
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
483
474
|
this.connectionStateHandler.dispose();
|
|
484
475
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
485
|
-
|
|
476
|
+
this.storageService.dispose();
|
|
486
477
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
487
478
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
488
479
|
// Driver need to ensure all caches are cleared on critical errors
|
|
489
|
-
(
|
|
480
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
490
481
|
}
|
|
491
482
|
catch (exception) {
|
|
492
483
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
@@ -573,7 +564,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
573
564
|
const resolvedUrl = this.service.resolvedUrl;
|
|
574
565
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
575
566
|
this._resolvedUrl = resolvedUrl;
|
|
576
|
-
await this.
|
|
567
|
+
await this.storageService.connectToService(this.service);
|
|
577
568
|
if (hasAttachmentBlobs) {
|
|
578
569
|
// upload blobs to storage
|
|
579
570
|
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
@@ -603,8 +594,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
603
594
|
}
|
|
604
595
|
this._attachState = AttachState.Attached;
|
|
605
596
|
this.emit("attached");
|
|
606
|
-
// Propagate current connection state through the system.
|
|
607
|
-
this.propagateConnectionState();
|
|
608
597
|
if (!this.closed) {
|
|
609
598
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
610
599
|
}
|
|
@@ -743,9 +732,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
743
732
|
/**
|
|
744
733
|
* Load container.
|
|
745
734
|
*
|
|
746
|
-
* @param specifiedVersion -
|
|
747
|
-
* - undefined - fetch latest snapshot
|
|
748
|
-
* - otherwise, version sha to load snapshot
|
|
735
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
749
736
|
*/
|
|
750
737
|
async load(specifiedVersion, loadMode, pendingLocalState) {
|
|
751
738
|
if (this._resolvedUrl === undefined) {
|
|
@@ -768,11 +755,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
768
755
|
this.connectToDeltaStream(connectionArgs);
|
|
769
756
|
}
|
|
770
757
|
if (!pendingLocalState) {
|
|
771
|
-
await this.
|
|
758
|
+
await this.storageService.connectToService(this.service);
|
|
772
759
|
}
|
|
773
760
|
else {
|
|
774
761
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
775
|
-
this.
|
|
762
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
776
763
|
}
|
|
777
764
|
this._attachState = AttachState.Attached;
|
|
778
765
|
// Fetch specified snapshot.
|
|
@@ -807,12 +794,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
807
794
|
}
|
|
808
795
|
// ...load in the existing quorum
|
|
809
796
|
// Initialize the protocol handler
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
797
|
+
if (pendingLocalState === undefined) {
|
|
798
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
this.initializeProtocolState(attributes, {
|
|
802
|
+
members: pendingLocalState.protocol.members,
|
|
803
|
+
proposals: pendingLocalState.protocol.proposals,
|
|
804
|
+
values: pendingLocalState.protocol.values,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
816
807
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
817
808
|
await this.instantiateContext(true, // existing
|
|
818
809
|
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
@@ -866,7 +857,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
866
857
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
867
858
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
868
859
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
869
|
-
this.
|
|
860
|
+
this.initializeProtocolState(attributes, {
|
|
870
861
|
members: [],
|
|
871
862
|
proposals: [],
|
|
872
863
|
values: qValues,
|
|
@@ -881,40 +872,22 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
881
872
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
882
873
|
}
|
|
883
874
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
884
|
-
this.
|
|
885
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
875
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
876
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
886
877
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
887
878
|
// Initialize the protocol handler
|
|
888
879
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
889
|
-
const qValues = await readAndParse(this.
|
|
880
|
+
const qValues = await readAndParse(this.storageService, baseTree.blobs.quorumValues);
|
|
890
881
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
891
|
-
this.
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
});
|
|
882
|
+
this.initializeProtocolState(attributes, {
|
|
883
|
+
members: [],
|
|
884
|
+
proposals: [],
|
|
885
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
886
|
+
});
|
|
897
887
|
await this.instantiateContextDetached(true, // existing
|
|
898
888
|
snapshotTree);
|
|
899
889
|
this.setLoaded();
|
|
900
890
|
}
|
|
901
|
-
async connectStorageService() {
|
|
902
|
-
var _a, _b;
|
|
903
|
-
if (this._storageService !== undefined) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
907
|
-
const storageService = await this.service.connectToStorage();
|
|
908
|
-
this._storageService =
|
|
909
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
910
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
911
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
912
|
-
this._storageService =
|
|
913
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
914
|
-
}
|
|
915
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
916
|
-
assert(((_a = storageService.policies) === null || _a === void 0 ? void 0 : _a.minBlobSize) === ((_b = this.storageService.policies) === null || _b === void 0 ? void 0 : _b.minBlobSize), 0x0e0 /* "lost minBlobSize policy" */);
|
|
917
|
-
}
|
|
918
891
|
async getDocumentAttributes(storage, tree) {
|
|
919
892
|
if (tree === undefined) {
|
|
920
893
|
return {
|
|
@@ -948,13 +921,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
948
921
|
readAndParse(storage, baseTree.blobs.quorumValues),
|
|
949
922
|
]);
|
|
950
923
|
}
|
|
951
|
-
|
|
952
|
-
return protocolHandler;
|
|
924
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
953
925
|
}
|
|
954
|
-
|
|
926
|
+
initializeProtocolState(attributes, quorumSnapshot) {
|
|
955
927
|
var _a, _b;
|
|
956
928
|
const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
957
|
-
const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, { key, value }), (_b = this._initialClients) !== null && _b !== void 0 ? _b : []);
|
|
929
|
+
const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })), (_b = this._initialClients) !== null && _b !== void 0 ? _b : []);
|
|
958
930
|
this._initialClients = undefined;
|
|
959
931
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
960
932
|
protocol.quorum.on("error", (error) => {
|
|
@@ -980,7 +952,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
980
952
|
});
|
|
981
953
|
}
|
|
982
954
|
});
|
|
983
|
-
|
|
955
|
+
// we need to make sure this member get set in a synchronous context,
|
|
956
|
+
// or other things can happen after the object that will be set is created, but not yet set
|
|
957
|
+
// this was breaking this._initialClients handling
|
|
958
|
+
//
|
|
959
|
+
this._protocolHandler = protocol;
|
|
984
960
|
}
|
|
985
961
|
captureProtocolSummary() {
|
|
986
962
|
const quorumSnapshot = this.protocolHandler.snapshot();
|
|
@@ -1064,10 +1040,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1064
1040
|
this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
|
|
1065
1041
|
}
|
|
1066
1042
|
}
|
|
1067
|
-
|
|
1068
|
-
this.deltaManager
|
|
1069
|
-
: undefined;
|
|
1070
|
-
this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details, deltaManagerForCatchingUp);
|
|
1043
|
+
this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
|
|
1071
1044
|
});
|
|
1072
1045
|
deltaManager.on("disconnect", (reason) => {
|
|
1073
1046
|
var _a;
|
|
@@ -1084,6 +1057,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1084
1057
|
this.emit("warning", warn);
|
|
1085
1058
|
});
|
|
1086
1059
|
deltaManager.on("readonly", (readonly) => {
|
|
1060
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1087
1061
|
this.emit("readonly", readonly);
|
|
1088
1062
|
});
|
|
1089
1063
|
deltaManager.on("closed", (error) => {
|
|
@@ -1126,12 +1100,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1126
1100
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1127
1101
|
}
|
|
1128
1102
|
}
|
|
1129
|
-
|
|
1130
|
-
connectionInitiationReason = "InitialConnect";
|
|
1131
|
-
}
|
|
1132
|
-
else {
|
|
1133
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1134
|
-
}
|
|
1103
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1135
1104
|
}
|
|
1136
1105
|
this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
|
|
1137
1106
|
durationFromDisconnected,
|
|
@@ -1142,49 +1111,64 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1142
1111
|
this.firstConnection = false;
|
|
1143
1112
|
}
|
|
1144
1113
|
}
|
|
1145
|
-
propagateConnectionState() {
|
|
1114
|
+
propagateConnectionState(initialTransition) {
|
|
1146
1115
|
var _a;
|
|
1116
|
+
// When container loaded, we want to propagate initial connection state.
|
|
1117
|
+
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1118
|
+
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1119
|
+
if (!initialTransition &&
|
|
1120
|
+
this.connectionState !== ConnectionState.Connected &&
|
|
1121
|
+
this.connectionState !== ConnectionState.Disconnected) {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const state = this.connectionState === ConnectionState.Connected;
|
|
1147
1125
|
const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
|
|
1148
1126
|
!this.firstConnection &&
|
|
1149
1127
|
this.connectionMode === "write";
|
|
1150
1128
|
if (logOpsOnReconnect) {
|
|
1151
1129
|
this.messageCountAfterDisconnection = 0;
|
|
1152
1130
|
}
|
|
1153
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1154
1131
|
// Both protocol and context should not be undefined if we got so far.
|
|
1155
|
-
|
|
1156
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1157
|
-
}
|
|
1132
|
+
this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
|
|
1158
1133
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1159
1134
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1160
1135
|
if (logOpsOnReconnect) {
|
|
1161
1136
|
this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
|
|
1162
1137
|
}
|
|
1163
1138
|
}
|
|
1139
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1164
1140
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1165
|
-
|
|
1166
|
-
switch (outboundMessageType) {
|
|
1141
|
+
switch (type) {
|
|
1167
1142
|
case MessageType.Operation:
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
// github #6451: this is only needed for staging so the server
|
|
1172
|
-
// know when the protocol tree is included
|
|
1173
|
-
// this can be removed once all clients send
|
|
1174
|
-
// protocol tree by default
|
|
1175
|
-
const summary = contents;
|
|
1176
|
-
if (summary.details === undefined) {
|
|
1177
|
-
summary.details = {};
|
|
1178
|
-
}
|
|
1179
|
-
summary.details.includesProtocolTree =
|
|
1180
|
-
this.options.summarizeProtocolTree === true;
|
|
1181
|
-
break;
|
|
1182
|
-
}
|
|
1143
|
+
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1144
|
+
case MessageType.Summarize:
|
|
1145
|
+
return this.submitSummaryMessage(contents);
|
|
1183
1146
|
default:
|
|
1184
1147
|
this.close(new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
|
|
1185
1148
|
return -1;
|
|
1186
1149
|
}
|
|
1187
|
-
|
|
1150
|
+
}
|
|
1151
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
1152
|
+
submitBatch(batch) {
|
|
1153
|
+
let clientSequenceNumber = -1;
|
|
1154
|
+
for (const message of batch) {
|
|
1155
|
+
clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
|
|
1156
|
+
message.metadata);
|
|
1157
|
+
}
|
|
1158
|
+
this._deltaManager.flush();
|
|
1159
|
+
return clientSequenceNumber;
|
|
1160
|
+
}
|
|
1161
|
+
submitSummaryMessage(summary) {
|
|
1162
|
+
// github #6451: this is only needed for staging so the server
|
|
1163
|
+
// know when the protocol tree is included
|
|
1164
|
+
// this can be removed once all clients send
|
|
1165
|
+
// protocol tree by default
|
|
1166
|
+
if (summary.details === undefined) {
|
|
1167
|
+
summary.details = {};
|
|
1168
|
+
}
|
|
1169
|
+
summary.details.includesProtocolTree =
|
|
1170
|
+
this.options.summarizeProtocolTree === true;
|
|
1171
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1188
1172
|
}
|
|
1189
1173
|
submitMessage(type, contents, batch, metadata) {
|
|
1190
1174
|
var _a;
|
|
@@ -1199,22 +1183,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1199
1183
|
processRemoteMessage(message) {
|
|
1200
1184
|
const local = this.clientId === message.clientId;
|
|
1201
1185
|
// Allow the protocol handler to process the message
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
}
|
|
1206
|
-
catch (error) {
|
|
1207
|
-
this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1208
|
-
}
|
|
1209
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1210
|
-
if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
|
|
1211
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1212
|
-
}
|
|
1213
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1214
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1215
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1216
|
-
this.context.process(message, local, undefined);
|
|
1217
|
-
}
|
|
1186
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1187
|
+
// Forward messages to the loaded runtime for processing
|
|
1188
|
+
this.context.process(message, local, undefined);
|
|
1218
1189
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1219
1190
|
if (this.activeConnection()) {
|
|
1220
1191
|
if (this.collabWindowTracker === undefined) {
|
|
@@ -1223,15 +1194,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1223
1194
|
// clients.
|
|
1224
1195
|
// All existing will continue to use settings they got earlier.
|
|
1225
1196
|
assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
|
|
1226
|
-
this.collabWindowTracker = new CollabWindowTracker((type
|
|
1197
|
+
this.collabWindowTracker = new CollabWindowTracker((type) => {
|
|
1227
1198
|
assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1228
|
-
this.submitMessage(type
|
|
1199
|
+
this.submitMessage(type);
|
|
1229
1200
|
}, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
|
|
1230
1201
|
}
|
|
1231
1202
|
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
1232
1203
|
}
|
|
1233
1204
|
this.emit("op", message);
|
|
1234
|
-
return result;
|
|
1235
1205
|
}
|
|
1236
1206
|
submitSignal(message) {
|
|
1237
1207
|
this._deltaManager.submitSignal(JSON.stringify(message));
|
|
@@ -1278,7 +1248,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1278
1248
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1279
1249
|
// are set. Global requests will still go directly to the loader
|
|
1280
1250
|
const loader = new RelativeLoader(this, this.loader);
|
|
1281
|
-
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1251
|
+
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1282
1252
|
this.emit("contextChanged", codeDetails);
|
|
1283
1253
|
}
|
|
1284
1254
|
updateDirtyContainerState(dirty) {
|
|
@@ -1291,6 +1261,24 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1291
1261
|
logContainerError(warning) {
|
|
1292
1262
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1293
1263
|
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Set the connected state of the ContainerContext
|
|
1266
|
+
* This controls the "connected" state of the ContainerRuntime as well
|
|
1267
|
+
* @param state - Is the container currently connected?
|
|
1268
|
+
* @param readonly - Is the container in readonly mode?
|
|
1269
|
+
*/
|
|
1270
|
+
setContextConnectedState(state, readonly) {
|
|
1271
|
+
var _a;
|
|
1272
|
+
if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1273
|
+
/**
|
|
1274
|
+
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1275
|
+
* ops getting through to the DeltaManager.
|
|
1276
|
+
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1277
|
+
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1278
|
+
*/
|
|
1279
|
+
this.context.setConnectionState(state && !readonly, this.clientId);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1294
1282
|
}
|
|
1295
1283
|
Container.version = "^0.1.0";
|
|
1296
1284
|
//# sourceMappingURL=container.js.map
|