@fluidframework/container-loader 2.0.0-internal.1.1.3 → 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 +107 -125
- 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 +110 -128
- 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 +132 -147
- 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;
|
|
@@ -1306,15 +1294,15 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1306
1294
|
}
|
|
1307
1295
|
|
|
1308
1296
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1309
|
-
this.
|
|
1310
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1297
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1298
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
1311
1299
|
|
|
1312
1300
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1313
1301
|
|
|
1314
1302
|
// Initialize the protocol handler
|
|
1315
1303
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1316
1304
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1317
|
-
this.
|
|
1305
|
+
this.storageService,
|
|
1318
1306
|
baseTree.blobs.quorumValues,
|
|
1319
1307
|
);
|
|
1320
1308
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
@@ -1335,28 +1323,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1335
1323
|
this.setLoaded();
|
|
1336
1324
|
}
|
|
1337
1325
|
|
|
1338
|
-
private async connectStorageService(): Promise<void> {
|
|
1339
|
-
if (this._storageService !== undefined) {
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
1344
|
-
const storageService = await this.service.connectToStorage();
|
|
1345
|
-
|
|
1346
|
-
this._storageService =
|
|
1347
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
1348
|
-
|
|
1349
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
1350
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
1351
|
-
this._storageService =
|
|
1352
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
1356
|
-
assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
|
|
1357
|
-
0x0e0 /* "lost minBlobSize policy" */);
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
1326
|
private async getDocumentAttributes(
|
|
1361
1327
|
storage: IDocumentStorageService,
|
|
1362
1328
|
tree: ISnapshotTree | undefined,
|
|
@@ -1416,7 +1382,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
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
|
|
|
@@ -1560,15 +1526,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1560
1526
|
}
|
|
1561
1527
|
}
|
|
1562
1528
|
|
|
1563
|
-
const deltaManagerForCatchingUp =
|
|
1564
|
-
this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
|
|
1565
|
-
this.deltaManager
|
|
1566
|
-
: undefined;
|
|
1567
|
-
|
|
1568
1529
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1569
1530
|
this.connectionMode,
|
|
1570
1531
|
details,
|
|
1571
|
-
deltaManagerForCatchingUp,
|
|
1572
1532
|
);
|
|
1573
1533
|
});
|
|
1574
1534
|
|
|
@@ -1588,6 +1548,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1588
1548
|
});
|
|
1589
1549
|
|
|
1590
1550
|
deltaManager.on("readonly", (readonly) => {
|
|
1551
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1591
1552
|
this.emit("readonly", readonly);
|
|
1592
1553
|
});
|
|
1593
1554
|
|
|
@@ -1642,11 +1603,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1642
1603
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1643
1604
|
}
|
|
1644
1605
|
}
|
|
1645
|
-
|
|
1646
|
-
connectionInitiationReason = "InitialConnect";
|
|
1647
|
-
} else {
|
|
1648
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1649
|
-
}
|
|
1606
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1650
1607
|
}
|
|
1651
1608
|
|
|
1652
1609
|
this.mc.logger.sendPerformanceEvent({
|
|
@@ -1672,7 +1629,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1672
1629
|
}
|
|
1673
1630
|
}
|
|
1674
1631
|
|
|
1675
|
-
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
|
+
|
|
1676
1643
|
const logOpsOnReconnect: boolean =
|
|
1677
1644
|
this.connectionState === ConnectionState.Connected &&
|
|
1678
1645
|
!this.firstConnection &&
|
|
@@ -1681,13 +1648,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1681
1648
|
this.messageCountAfterDisconnection = 0;
|
|
1682
1649
|
}
|
|
1683
1650
|
|
|
1684
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1685
|
-
|
|
1686
1651
|
// Both protocol and context should not be undefined if we got so far.
|
|
1687
1652
|
|
|
1688
|
-
|
|
1689
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1690
|
-
}
|
|
1653
|
+
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
1691
1654
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1692
1655
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1693
1656
|
|
|
@@ -1697,35 +1660,53 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1697
1660
|
}
|
|
1698
1661
|
}
|
|
1699
1662
|
|
|
1663
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1700
1664
|
private submitContainerMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
|
|
1701
|
-
|
|
1702
|
-
switch (outboundMessageType) {
|
|
1665
|
+
switch (type) {
|
|
1703
1666
|
case MessageType.Operation:
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
const summary = contents as ISummaryContent;
|
|
1712
|
-
if (summary.details === undefined) {
|
|
1713
|
-
summary.details = {};
|
|
1714
|
-
}
|
|
1715
|
-
summary.details.includesProtocolTree =
|
|
1716
|
-
this.options.summarizeProtocolTree === true;
|
|
1717
|
-
break;
|
|
1718
|
-
}
|
|
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);
|
|
1719
1674
|
default:
|
|
1720
1675
|
this.close(new GenericError("invalidContainerSubmitOpType",
|
|
1721
1676
|
undefined /* error */,
|
|
1722
1677
|
{ messageType: type }));
|
|
1723
1678
|
return -1;
|
|
1724
1679
|
}
|
|
1725
|
-
return this.submitMessage(type, contents, batch, metadata);
|
|
1726
1680
|
}
|
|
1727
1681
|
|
|
1728
|
-
|
|
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 {
|
|
1729
1710
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1730
1711
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1731
1712
|
return -1;
|
|
@@ -1736,28 +1717,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1736
1717
|
return this._deltaManager.submit(type, contents, batch, metadata);
|
|
1737
1718
|
}
|
|
1738
1719
|
|
|
1739
|
-
private processRemoteMessage(message: ISequencedDocumentMessage)
|
|
1720
|
+
private processRemoteMessage(message: ISequencedDocumentMessage) {
|
|
1740
1721
|
const local = this.clientId === message.clientId;
|
|
1741
1722
|
|
|
1742
1723
|
// Allow the protocol handler to process the message
|
|
1743
|
-
|
|
1744
|
-
try {
|
|
1745
|
-
result = this.protocolHandler.processMessage(message, local);
|
|
1746
|
-
} catch (error) {
|
|
1747
|
-
this.close(wrapError(error, (errorMessage) =>
|
|
1748
|
-
new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1749
|
-
}
|
|
1724
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1750
1725
|
|
|
1751
|
-
//
|
|
1752
|
-
|
|
1753
|
-
this.mc.logger.sendTelemetryEvent(
|
|
1754
|
-
{ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1755
|
-
}
|
|
1756
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1757
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1758
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1759
|
-
this.context.process(message, local, undefined);
|
|
1760
|
-
}
|
|
1726
|
+
// Forward messages to the loaded runtime for processing
|
|
1727
|
+
this.context.process(message, local, undefined);
|
|
1761
1728
|
|
|
1762
1729
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1763
1730
|
if (this.activeConnection()) {
|
|
@@ -1770,10 +1737,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1770
1737
|
this.serviceConfiguration !== undefined,
|
|
1771
1738
|
0x2e4 /* "there should be service config for active connection" */);
|
|
1772
1739
|
this.collabWindowTracker = new CollabWindowTracker(
|
|
1773
|
-
(type
|
|
1740
|
+
(type) => {
|
|
1774
1741
|
assert(this.activeConnection(),
|
|
1775
1742
|
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1776
|
-
this.submitMessage(type
|
|
1743
|
+
this.submitMessage(type);
|
|
1777
1744
|
},
|
|
1778
1745
|
this.serviceConfiguration.noopTimeFrequency,
|
|
1779
1746
|
this.serviceConfiguration.noopCountFrequency,
|
|
@@ -1783,8 +1750,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1783
1750
|
}
|
|
1784
1751
|
|
|
1785
1752
|
this.emit("op", message);
|
|
1786
|
-
|
|
1787
|
-
return result;
|
|
1788
1753
|
}
|
|
1789
1754
|
|
|
1790
1755
|
private submitSignal(message: any) {
|
|
@@ -1860,6 +1825,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1860
1825
|
new QuorumProxy(this.protocolHandler.quorum),
|
|
1861
1826
|
loader,
|
|
1862
1827
|
(type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata),
|
|
1828
|
+
(summaryOp: ISummaryContent) => this.submitSummaryMessage(summaryOp),
|
|
1829
|
+
(batch: IBatchMessage[]) => this.submitBatch(batch),
|
|
1863
1830
|
(message) => this.submitSignal(message),
|
|
1864
1831
|
(error?: ICriticalContainerError) => this.close(error),
|
|
1865
1832
|
Container.version,
|
|
@@ -1882,4 +1849,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1882
1849
|
private logContainerError(warning: ContainerWarning) {
|
|
1883
1850
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1884
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
|
+
}
|
|
1885
1870
|
}
|
package/src/containerContext.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import {
|
|
7
|
+
import { LazyPromise } from "@fluidframework/common-utils";
|
|
8
8
|
import {
|
|
9
9
|
IAudience,
|
|
10
10
|
IContainerContext,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ICodeDetailsLoader,
|
|
23
23
|
IFluidModuleWithDetails,
|
|
24
24
|
ISnapshotTreeWithBlobContents,
|
|
25
|
+
IBatchMessage,
|
|
25
26
|
} from "@fluidframework/container-definitions";
|
|
26
27
|
import {
|
|
27
28
|
IRequest,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
ISummaryTree,
|
|
43
44
|
IVersion,
|
|
44
45
|
MessageType,
|
|
46
|
+
ISummaryContent,
|
|
45
47
|
} from "@fluidframework/protocol-definitions";
|
|
46
48
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
47
49
|
import { Container } from "./container";
|
|
@@ -59,6 +61,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
59
61
|
quorum: IQuorum,
|
|
60
62
|
loader: ILoader,
|
|
61
63
|
submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
64
|
+
submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
65
|
+
submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
62
66
|
submitSignalFn: (contents: any) => void,
|
|
63
67
|
closeFn: (error?: ICriticalContainerError) => void,
|
|
64
68
|
version: string,
|
|
@@ -76,6 +80,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
76
80
|
quorum,
|
|
77
81
|
loader,
|
|
78
82
|
submitFn,
|
|
83
|
+
submitSummaryFn,
|
|
84
|
+
submitBatchFn,
|
|
79
85
|
submitSignalFn,
|
|
80
86
|
closeFn,
|
|
81
87
|
version,
|
|
@@ -107,8 +113,13 @@ export class ContainerContext implements IContainerContext {
|
|
|
107
113
|
return this.container.clientDetails;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
private _connected: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* When true, ops are free to flow
|
|
119
|
+
* When false, ops should be kept as pending or rejected
|
|
120
|
+
*/
|
|
110
121
|
public get connected(): boolean {
|
|
111
|
-
return this.
|
|
122
|
+
return this._connected;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
public get canSummarize(): boolean {
|
|
@@ -166,6 +177,9 @@ export class ContainerContext implements IContainerContext {
|
|
|
166
177
|
quorum: IQuorum,
|
|
167
178
|
public readonly loader: ILoader,
|
|
168
179
|
public readonly submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
180
|
+
public readonly submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
181
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
182
|
+
public readonly submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
169
183
|
public readonly submitSignalFn: (contents: any) => void,
|
|
170
184
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
171
185
|
public readonly version: string,
|
|
@@ -174,6 +188,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
174
188
|
public readonly pendingLocalState?: unknown,
|
|
175
189
|
|
|
176
190
|
) {
|
|
191
|
+
this._connected = this.container.connected;
|
|
177
192
|
this._quorum = quorum;
|
|
178
193
|
this.taggedLogger = container.subLogger;
|
|
179
194
|
this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(
|
|
@@ -183,9 +198,10 @@ export class ContainerContext implements IContainerContext {
|
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
/**
|
|
186
|
-
* @deprecated
|
|
187
|
-
* ContainerContext should only take an IQuorumClients
|
|
188
|
-
*
|
|
201
|
+
* @deprecated Temporary migratory API, to be removed when customers no longer need it.
|
|
202
|
+
* When removed, `ContainerContext` should only take an {@link @fluidframework/container-definitions#IQuorumClients}
|
|
203
|
+
* rather than an {@link @fluidframework/protocol-definitions#IQuorum}.
|
|
204
|
+
* See {@link @fluidframework/container-definitions#IContainerContext} for more details.
|
|
189
205
|
*/
|
|
190
206
|
public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
|
|
191
207
|
return (this._quorum.get("code") ?? this._quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
@@ -223,9 +239,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
223
239
|
|
|
224
240
|
public setConnectionState(connected: boolean, clientId?: string) {
|
|
225
241
|
const runtime = this.runtime;
|
|
226
|
-
|
|
227
|
-
assert(connected === this.connected, 0x0de /* "Mismatch in connection state while setting" */);
|
|
228
|
-
|
|
242
|
+
this._connected = connected;
|
|
229
243
|
runtime.setConnectionState(connected, clientId);
|
|
230
244
|
}
|
|
231
245
|
|