@fluidframework/container-loader 1.3.0 → 2.0.0-dev.1.4.5.105745
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/.eslintrc.js +8 -21
- package/.mocharc.js +12 -0
- package/dist/audience.d.ts +2 -2
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +29 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/catchUpMonitor.js +43 -0
- package/dist/catchUpMonitor.js.map +1 -0
- package/dist/collabWindowTracker.d.ts +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js +12 -4
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +13 -18
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts +0 -5
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js +0 -5
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +84 -22
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +172 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +30 -17
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +173 -165
- 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 +11 -25
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +51 -17
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +5 -5
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +4 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +33 -6
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.js +3 -3
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +8 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -3
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +22 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +53 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +2 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/lib/audience.d.ts +2 -2
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +29 -0
- package/lib/catchUpMonitor.d.ts.map +1 -0
- package/lib/catchUpMonitor.js +39 -0
- package/lib/catchUpMonitor.js.map +1 -0
- package/lib/collabWindowTracker.d.ts +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js +13 -5
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +14 -21
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts +0 -5
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js +0 -5
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +84 -22
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +171 -59
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +30 -17
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +176 -168
- 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 +11 -25
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +51 -16
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +5 -5
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +4 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +35 -8
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.js +3 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +8 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +4 -3
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +22 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +49 -0
- package/lib/protocol.js.map +1 -0
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/package.json +26 -20
- package/src/audience.ts +2 -2
- package/src/catchUpMonitor.ts +59 -0
- package/src/collabWindowTracker.ts +15 -6
- package/src/connectionManager.ts +23 -27
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +235 -70
- package/src/container.ts +223 -209
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +71 -16
- package/src/contracts.ts +7 -7
- package/src/deltaManager.ts +42 -11
- package/src/deltaQueue.ts +3 -3
- package/src/index.ts +4 -0
- package/src/loader.ts +14 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +97 -0
- package/src/retriableDocumentStorageService.ts +8 -2
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,13 +49,8 @@ import {
|
|
|
50
49
|
combineAppAndProtocolSummary,
|
|
51
50
|
runWithRetry,
|
|
52
51
|
isFluidResolvedUrl,
|
|
53
|
-
isRuntimeMessage,
|
|
54
|
-
isUnpackedRuntimeMessage,
|
|
55
52
|
} from "@fluidframework/driver-utils";
|
|
56
|
-
import {
|
|
57
|
-
IProtocolHandler,
|
|
58
|
-
ProtocolOpHandlerWithClientValidation,
|
|
59
|
-
} from "@fluidframework/protocol-base";
|
|
53
|
+
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
60
54
|
import {
|
|
61
55
|
IClient,
|
|
62
56
|
IClientConfiguration,
|
|
@@ -64,7 +58,6 @@ import {
|
|
|
64
58
|
ICommittedProposal,
|
|
65
59
|
IDocumentAttributes,
|
|
66
60
|
IDocumentMessage,
|
|
67
|
-
IProcessMessageResult,
|
|
68
61
|
IProtocolState,
|
|
69
62
|
IQuorumClients,
|
|
70
63
|
IQuorumProposals,
|
|
@@ -100,15 +93,21 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
100
93
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
101
94
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
102
95
|
import { pkgVersion } from "./packageVersion";
|
|
103
|
-
import {
|
|
104
|
-
import {
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
97
|
+
import {
|
|
98
|
+
IConnectionStateHandler,
|
|
99
|
+
createConnectionStateHandler,
|
|
100
|
+
} from "./connectionStateHandler";
|
|
107
101
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
108
102
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
109
103
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
110
104
|
import { ConnectionManager } from "./connectionManager";
|
|
111
105
|
import { ConnectionState } from "./connectionState";
|
|
106
|
+
import {
|
|
107
|
+
IProtocolHandler,
|
|
108
|
+
ProtocolHandler,
|
|
109
|
+
ProtocolHandlerBuilder,
|
|
110
|
+
} from "./protocol";
|
|
112
111
|
|
|
113
112
|
const detachedContainerRefSeqNumber = 0;
|
|
114
113
|
|
|
@@ -149,14 +148,19 @@ export interface IContainerConfig {
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
/**
|
|
152
|
-
* 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
|
+
*
|
|
153
153
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
154
154
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
155
|
+
*
|
|
155
156
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
156
157
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
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
|
+
*
|
|
160
164
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
161
165
|
*/
|
|
162
166
|
export async function waitContainerToCatchUp(container: IContainer) {
|
|
@@ -179,6 +183,10 @@ export async function waitContainerToCatchUp(container: IContainer) {
|
|
|
179
183
|
};
|
|
180
184
|
container.on("closed", closedCallback);
|
|
181
185
|
|
|
186
|
+
// Depending on config, transition to "connected" state may include the guarantee
|
|
187
|
+
// that all known ops have been processed. If so, we may introduce additional wait here.
|
|
188
|
+
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
189
|
+
// which is a reasonable approximation of "caught up"
|
|
182
190
|
const waitForOps = () => {
|
|
183
191
|
assert(container.connectionState === ConnectionState.CatchingUp
|
|
184
192
|
|| container.connectionState === ConnectionState.Connected,
|
|
@@ -270,6 +278,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
270
278
|
loader: Loader,
|
|
271
279
|
loadOptions: IContainerLoadOptions,
|
|
272
280
|
pendingLocalState?: IPendingContainerState,
|
|
281
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
273
282
|
): Promise<Container> {
|
|
274
283
|
const container = new Container(
|
|
275
284
|
loader,
|
|
@@ -278,7 +287,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
278
287
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
279
288
|
canReconnect: loadOptions.canReconnect,
|
|
280
289
|
serializedContainerState: pendingLocalState,
|
|
281
|
-
}
|
|
290
|
+
},
|
|
291
|
+
protocolHandlerBuilder);
|
|
282
292
|
|
|
283
293
|
return PerformanceEvent.timedExecAsync(
|
|
284
294
|
container.mc.logger,
|
|
@@ -326,10 +336,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
326
336
|
public static async createDetached(
|
|
327
337
|
loader: Loader,
|
|
328
338
|
codeDetails: IFluidCodeDetails,
|
|
339
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
329
340
|
): Promise<Container> {
|
|
330
341
|
const container = new Container(
|
|
331
342
|
loader,
|
|
332
|
-
{}
|
|
343
|
+
{},
|
|
344
|
+
protocolHandlerBuilder);
|
|
333
345
|
|
|
334
346
|
return PerformanceEvent.timedExecAsync(
|
|
335
347
|
container.mc.logger,
|
|
@@ -348,10 +360,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
348
360
|
public static async rehydrateDetachedFromSnapshot(
|
|
349
361
|
loader: Loader,
|
|
350
362
|
snapshot: string,
|
|
363
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
351
364
|
): Promise<Container> {
|
|
352
365
|
const container = new Container(
|
|
353
366
|
loader,
|
|
354
|
-
{}
|
|
367
|
+
{},
|
|
368
|
+
protocolHandlerBuilder);
|
|
369
|
+
|
|
355
370
|
return PerformanceEvent.timedExecAsync(
|
|
356
371
|
container.mc.logger,
|
|
357
372
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
@@ -378,7 +393,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
378
393
|
// Only transition states if currently loading
|
|
379
394
|
if (this._lifecycleState === "loading") {
|
|
380
395
|
// Propagate current connection state through the system.
|
|
381
|
-
this.propagateConnectionState();
|
|
396
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
382
397
|
this._lifecycleState = "loaded";
|
|
383
398
|
}
|
|
384
399
|
}
|
|
@@ -389,23 +404,15 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
389
404
|
|
|
390
405
|
private _attachState = AttachState.Detached;
|
|
391
406
|
|
|
392
|
-
private readonly
|
|
407
|
+
private readonly storageService: ContainerStorageAdapter;
|
|
393
408
|
public get storage(): IDocumentStorageService {
|
|
394
|
-
return this.
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
private _storageService: IDocumentStorageService & IDisposable | undefined;
|
|
398
|
-
private get storageService(): IDocumentStorageService {
|
|
399
|
-
if (this._storageService === undefined) {
|
|
400
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
401
|
-
}
|
|
402
|
-
return this._storageService;
|
|
409
|
+
return this.storageService;
|
|
403
410
|
}
|
|
404
411
|
|
|
405
412
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
406
413
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
407
414
|
private service: IDocumentService | undefined;
|
|
408
|
-
private
|
|
415
|
+
private _initialClients: ISignalClient[] | undefined;
|
|
409
416
|
|
|
410
417
|
private _context: ContainerContext | undefined;
|
|
411
418
|
private get context() {
|
|
@@ -434,7 +441,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
434
441
|
|
|
435
442
|
private lastVisible: number | undefined;
|
|
436
443
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
437
|
-
private readonly connectionStateHandler:
|
|
444
|
+
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
438
445
|
|
|
439
446
|
private setAutoReconnectTime = performance.now();
|
|
440
447
|
|
|
@@ -476,7 +483,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
476
483
|
}
|
|
477
484
|
|
|
478
485
|
public get connected(): boolean {
|
|
479
|
-
return this.connectionStateHandler.
|
|
486
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
480
487
|
}
|
|
481
488
|
|
|
482
489
|
/**
|
|
@@ -487,12 +494,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
487
494
|
return this._deltaManager.serviceConfiguration;
|
|
488
495
|
}
|
|
489
496
|
|
|
497
|
+
private _clientId: string | undefined;
|
|
498
|
+
|
|
490
499
|
/**
|
|
491
500
|
* The server provided id of the client.
|
|
492
501
|
* Set once this.connected is true, otherwise undefined
|
|
493
502
|
*/
|
|
494
503
|
public get clientId(): string | undefined {
|
|
495
|
-
return this.
|
|
504
|
+
return this._clientId;
|
|
496
505
|
}
|
|
497
506
|
|
|
498
507
|
/**
|
|
@@ -528,13 +537,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
528
537
|
* Retrieves the audience associated with the document
|
|
529
538
|
*/
|
|
530
539
|
public get audience(): IAudience {
|
|
531
|
-
return this.
|
|
540
|
+
return this.protocolHandler.audience;
|
|
532
541
|
}
|
|
533
542
|
|
|
534
543
|
/**
|
|
535
544
|
* Returns true if container is dirty.
|
|
536
545
|
* Which means data loss if container is closed at that same moment
|
|
537
|
-
* Most likely that happens when there is no network connection to
|
|
546
|
+
* Most likely that happens when there is no network connection to Relay Service
|
|
538
547
|
*/
|
|
539
548
|
public get isDirty() {
|
|
540
549
|
return this._dirtyContainer;
|
|
@@ -549,6 +558,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
549
558
|
constructor(
|
|
550
559
|
private readonly loader: Loader,
|
|
551
560
|
config: IContainerConfig,
|
|
561
|
+
private readonly protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
552
562
|
) {
|
|
553
563
|
super((name, error) => {
|
|
554
564
|
this.mc.logger.sendErrorEvent(
|
|
@@ -558,7 +568,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
558
568
|
},
|
|
559
569
|
error);
|
|
560
570
|
});
|
|
561
|
-
this._audience = new Audience();
|
|
562
571
|
|
|
563
572
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
564
573
|
this._resolvedUrl = config.resolvedUrl;
|
|
@@ -618,11 +627,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
618
627
|
summarizeProtocolTree,
|
|
619
628
|
};
|
|
620
629
|
|
|
621
|
-
this.
|
|
630
|
+
this._deltaManager = this.createDeltaManager();
|
|
631
|
+
|
|
632
|
+
this._clientId = config.serializedContainerState?.clientId;
|
|
633
|
+
this.connectionStateHandler = createConnectionStateHandler(
|
|
622
634
|
{
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
},
|
|
626
645
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
627
646
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
628
647
|
logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
|
|
@@ -634,35 +653,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
634
653
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
635
654
|
});
|
|
636
655
|
},
|
|
637
|
-
connectionStateChanged: () => {
|
|
638
|
-
// Fire events only if container is fully loaded and not closed
|
|
639
|
-
if (this._lifecycleState === "loaded") {
|
|
640
|
-
this.propagateConnectionState();
|
|
641
|
-
}
|
|
642
|
-
},
|
|
643
656
|
},
|
|
644
|
-
this.
|
|
645
|
-
|
|
657
|
+
this.deltaManager,
|
|
658
|
+
this._clientId,
|
|
646
659
|
);
|
|
647
660
|
|
|
648
661
|
this.on(savedContainerEvent, () => {
|
|
649
662
|
this.connectionStateHandler.containerSaved();
|
|
650
663
|
});
|
|
651
664
|
|
|
652
|
-
this.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
this.mc.logger.sendErrorEvent({
|
|
660
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
661
|
-
});
|
|
662
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
663
|
-
}
|
|
664
|
-
return this.storageService;
|
|
665
|
-
},
|
|
665
|
+
this.storageService = new ContainerStorageAdapter(
|
|
666
|
+
this.loader.services.detachedBlobStorage,
|
|
667
|
+
this.mc.logger,
|
|
668
|
+
this.options.summarizeProtocolTree === true
|
|
669
|
+
? () => this.captureProtocolSummary()
|
|
670
|
+
: undefined,
|
|
666
671
|
);
|
|
667
672
|
|
|
668
673
|
const isDomAvailable = typeof document === "object" &&
|
|
@@ -762,7 +767,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
762
767
|
|
|
763
768
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
764
769
|
|
|
765
|
-
this.
|
|
770
|
+
this.storageService.dispose();
|
|
766
771
|
|
|
767
772
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
768
773
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -787,13 +792,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
787
792
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
788
793
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
789
794
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
790
|
-
|
|
791
795
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
792
796
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
793
797
|
0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
794
798
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
795
799
|
assert(this._protocolHandler.attributes.term !== undefined,
|
|
796
|
-
|
|
800
|
+
0x37e /* Must have a valid protocol handler instance */);
|
|
797
801
|
const pendingState: IPendingContainerState = {
|
|
798
802
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
799
803
|
url: this.resolvedUrl.url,
|
|
@@ -802,6 +806,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
802
806
|
clientId: this.clientId,
|
|
803
807
|
};
|
|
804
808
|
|
|
809
|
+
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
810
|
+
|
|
805
811
|
this.close();
|
|
806
812
|
|
|
807
813
|
return JSON.stringify(pendingState);
|
|
@@ -883,7 +889,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
883
889
|
const resolvedUrl = this.service.resolvedUrl;
|
|
884
890
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
885
891
|
this._resolvedUrl = resolvedUrl;
|
|
886
|
-
await this.
|
|
892
|
+
await this.storageService.connectToService(this.service);
|
|
887
893
|
|
|
888
894
|
if (hasAttachmentBlobs) {
|
|
889
895
|
// upload blobs to storage
|
|
@@ -921,8 +927,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
921
927
|
this._attachState = AttachState.Attached;
|
|
922
928
|
this.emit("attached");
|
|
923
929
|
|
|
924
|
-
// Propagate current connection state through the system.
|
|
925
|
-
this.propagateConnectionState();
|
|
926
930
|
if (!this.closed) {
|
|
927
931
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
928
932
|
}
|
|
@@ -1098,9 +1102,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1098
1102
|
/**
|
|
1099
1103
|
* Load container.
|
|
1100
1104
|
*
|
|
1101
|
-
* @param specifiedVersion -
|
|
1102
|
-
* - undefined - fetch latest snapshot
|
|
1103
|
-
* - otherwise, version sha to load snapshot
|
|
1105
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
1104
1106
|
*/
|
|
1105
1107
|
private async load(
|
|
1106
1108
|
specifiedVersion: string | undefined,
|
|
@@ -1134,10 +1136,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1134
1136
|
}
|
|
1135
1137
|
|
|
1136
1138
|
if (!pendingLocalState) {
|
|
1137
|
-
await this.
|
|
1139
|
+
await this.storageService.connectToService(this.service);
|
|
1138
1140
|
} else {
|
|
1139
1141
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1140
|
-
this.
|
|
1142
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
1141
1143
|
}
|
|
1142
1144
|
|
|
1143
1145
|
this._attachState = AttachState.Attached;
|
|
@@ -1178,14 +1180,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1178
1180
|
|
|
1179
1181
|
// ...load in the existing quorum
|
|
1180
1182
|
// Initialize the protocol handler
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1183
|
+
if (pendingLocalState === undefined) {
|
|
1184
|
+
await this.initializeProtocolStateFromSnapshot(
|
|
1185
|
+
attributes,
|
|
1186
|
+
this.storageService,
|
|
1187
|
+
snapshot);
|
|
1188
|
+
} else {
|
|
1189
|
+
this.initializeProtocolState(
|
|
1184
1190
|
attributes,
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1191
|
+
{
|
|
1192
|
+
members: pendingLocalState.protocol.members,
|
|
1193
|
+
proposals: pendingLocalState.protocol.proposals,
|
|
1194
|
+
values: pendingLocalState.protocol.values,
|
|
1195
|
+
}, // pending IQuorumSnapshot
|
|
1188
1196
|
);
|
|
1197
|
+
}
|
|
1189
1198
|
|
|
1190
1199
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1191
1200
|
await this.instantiateContext(
|
|
@@ -1260,11 +1269,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1260
1269
|
|
|
1261
1270
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1262
1271
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
1263
|
-
this.
|
|
1272
|
+
this.initializeProtocolState(
|
|
1264
1273
|
attributes,
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1274
|
+
{
|
|
1275
|
+
members: [],
|
|
1276
|
+
proposals: [],
|
|
1277
|
+
values: qValues,
|
|
1278
|
+
}, // IQuorumSnapShot
|
|
1268
1279
|
);
|
|
1269
1280
|
|
|
1270
1281
|
// The load context - given we seeded the quorum - will be great
|
|
@@ -1283,24 +1294,26 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1283
1294
|
}
|
|
1284
1295
|
|
|
1285
1296
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1286
|
-
this.
|
|
1287
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1297
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1298
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
1288
1299
|
|
|
1289
1300
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1290
1301
|
|
|
1291
1302
|
// Initialize the protocol handler
|
|
1292
1303
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1293
1304
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1294
|
-
this.
|
|
1305
|
+
this.storageService,
|
|
1295
1306
|
baseTree.blobs.quorumValues,
|
|
1296
1307
|
);
|
|
1297
1308
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1298
|
-
this.
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
[],
|
|
1302
|
-
[],
|
|
1303
|
-
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []
|
|
1309
|
+
this.initializeProtocolState(
|
|
1310
|
+
attributes,
|
|
1311
|
+
{
|
|
1312
|
+
members: [],
|
|
1313
|
+
proposals: [],
|
|
1314
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1315
|
+
}, // IQuorumSnapShot
|
|
1316
|
+
);
|
|
1304
1317
|
|
|
1305
1318
|
await this.instantiateContextDetached(
|
|
1306
1319
|
true, // existing
|
|
@@ -1310,28 +1323,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1310
1323
|
this.setLoaded();
|
|
1311
1324
|
}
|
|
1312
1325
|
|
|
1313
|
-
private async connectStorageService(): Promise<void> {
|
|
1314
|
-
if (this._storageService !== undefined) {
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
1319
|
-
const storageService = await this.service.connectToStorage();
|
|
1320
|
-
|
|
1321
|
-
this._storageService =
|
|
1322
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
1323
|
-
|
|
1324
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
1325
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
1326
|
-
this._storageService =
|
|
1327
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
1331
|
-
assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
|
|
1332
|
-
0x0e0 /* "lost minBlobSize policy" */);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
1326
|
private async getDocumentAttributes(
|
|
1336
1327
|
storage: IDocumentStorageService,
|
|
1337
1328
|
tree: ISnapshotTree | undefined,
|
|
@@ -1363,45 +1354,40 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1363
1354
|
attributes: IDocumentAttributes,
|
|
1364
1355
|
storage: IDocumentStorageService,
|
|
1365
1356
|
snapshot: ISnapshotTree | undefined,
|
|
1366
|
-
): Promise<
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1357
|
+
): Promise<void> {
|
|
1358
|
+
const quorumSnapshot: IQuorumSnapshot = {
|
|
1359
|
+
members: [],
|
|
1360
|
+
proposals: [],
|
|
1361
|
+
values: [],
|
|
1362
|
+
};
|
|
1370
1363
|
|
|
1371
1364
|
if (snapshot !== undefined) {
|
|
1372
1365
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
1373
|
-
[members, proposals, values] = await Promise.all([
|
|
1366
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
|
|
1374
1367
|
readAndParse<[string, ISequencedClient][]>(storage, baseTree.blobs.quorumMembers),
|
|
1375
1368
|
readAndParse<[number, ISequencedProposal, string[]][]>(storage, baseTree.blobs.quorumProposals),
|
|
1376
1369
|
readAndParse<[string, ICommittedProposal][]>(storage, baseTree.blobs.quorumValues),
|
|
1377
1370
|
]);
|
|
1378
1371
|
}
|
|
1379
1372
|
|
|
1380
|
-
|
|
1381
|
-
attributes,
|
|
1382
|
-
members,
|
|
1383
|
-
proposals,
|
|
1384
|
-
values);
|
|
1385
|
-
|
|
1386
|
-
return protocolHandler;
|
|
1373
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1387
1374
|
}
|
|
1388
1375
|
|
|
1389
|
-
private
|
|
1376
|
+
private initializeProtocolState(
|
|
1390
1377
|
attributes: IDocumentAttributes,
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const protocol =
|
|
1396
|
-
attributes
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
proposals,
|
|
1401
|
-
values,
|
|
1402
|
-
(key, value) => this.submitMessage(MessageType.Propose, { key, value }),
|
|
1378
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
1379
|
+
): void {
|
|
1380
|
+
const protocolHandlerBuilder =
|
|
1381
|
+
this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1382
|
+
const protocol = protocolHandlerBuilder(
|
|
1383
|
+
attributes,
|
|
1384
|
+
quorumSnapshot,
|
|
1385
|
+
(key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1386
|
+
this._initialClients ?? [],
|
|
1403
1387
|
);
|
|
1404
1388
|
|
|
1389
|
+
this._initialClients = undefined;
|
|
1390
|
+
|
|
1405
1391
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
1406
1392
|
|
|
1407
1393
|
protocol.quorum.on("error", (error) => {
|
|
@@ -1432,8 +1418,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1432
1418
|
});
|
|
1433
1419
|
}
|
|
1434
1420
|
});
|
|
1435
|
-
|
|
1436
|
-
|
|
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;
|
|
1437
1426
|
}
|
|
1438
1427
|
|
|
1439
1428
|
private captureProtocolSummary(): ISummaryTree {
|
|
@@ -1522,12 +1511,19 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1522
1511
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1523
1512
|
deltaManager.inboundSignal.pause();
|
|
1524
1513
|
|
|
1525
|
-
deltaManager.on("connect", (details: IConnectionDetails,
|
|
1526
|
-
|
|
1527
|
-
|
|
1514
|
+
deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
|
|
1515
|
+
if (this._protocolHandler === undefined) {
|
|
1516
|
+
// Store the initial clients so that they can be submitted to the
|
|
1517
|
+
// protocol handler when it is created.
|
|
1518
|
+
this._initialClients = details.initialClients;
|
|
1519
|
+
} else {
|
|
1520
|
+
// When reconnecting, the protocol handler is already created,
|
|
1521
|
+
// so we can update the audience right now.
|
|
1522
|
+
this._protocolHandler.audience.clear();
|
|
1528
1523
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1524
|
+
for (const priorClient of details.initialClients ?? []) {
|
|
1525
|
+
this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
|
|
1526
|
+
}
|
|
1531
1527
|
}
|
|
1532
1528
|
|
|
1533
1529
|
this.connectionStateHandler.receivedConnectEvent(
|
|
@@ -1552,6 +1548,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1552
1548
|
});
|
|
1553
1549
|
|
|
1554
1550
|
deltaManager.on("readonly", (readonly) => {
|
|
1551
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1555
1552
|
this.emit("readonly", readonly);
|
|
1556
1553
|
});
|
|
1557
1554
|
|
|
@@ -1606,11 +1603,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1606
1603
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1607
1604
|
}
|
|
1608
1605
|
}
|
|
1609
|
-
|
|
1610
|
-
connectionInitiationReason = "InitialConnect";
|
|
1611
|
-
} else {
|
|
1612
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1613
|
-
}
|
|
1606
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1614
1607
|
}
|
|
1615
1608
|
|
|
1616
1609
|
this.mc.logger.sendPerformanceEvent({
|
|
@@ -1636,7 +1629,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1636
1629
|
}
|
|
1637
1630
|
}
|
|
1638
1631
|
|
|
1639
|
-
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
|
+
|
|
1640
1643
|
const logOpsOnReconnect: boolean =
|
|
1641
1644
|
this.connectionState === ConnectionState.Connected &&
|
|
1642
1645
|
!this.firstConnection &&
|
|
@@ -1645,13 +1648,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1645
1648
|
this.messageCountAfterDisconnection = 0;
|
|
1646
1649
|
}
|
|
1647
1650
|
|
|
1648
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1649
|
-
|
|
1650
1651
|
// Both protocol and context should not be undefined if we got so far.
|
|
1651
1652
|
|
|
1652
|
-
|
|
1653
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1654
|
-
}
|
|
1653
|
+
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
1655
1654
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1656
1655
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1657
1656
|
|
|
@@ -1661,35 +1660,53 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1661
1660
|
}
|
|
1662
1661
|
}
|
|
1663
1662
|
|
|
1663
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1664
1664
|
private submitContainerMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
|
|
1665
|
-
|
|
1666
|
-
switch (outboundMessageType) {
|
|
1665
|
+
switch (type) {
|
|
1667
1666
|
case MessageType.Operation:
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const summary = contents as ISummaryContent;
|
|
1676
|
-
if (summary.details === undefined) {
|
|
1677
|
-
summary.details = {};
|
|
1678
|
-
}
|
|
1679
|
-
summary.details.includesProtocolTree =
|
|
1680
|
-
this.options.summarizeProtocolTree === true;
|
|
1681
|
-
break;
|
|
1682
|
-
}
|
|
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);
|
|
1683
1674
|
default:
|
|
1684
1675
|
this.close(new GenericError("invalidContainerSubmitOpType",
|
|
1685
1676
|
undefined /* error */,
|
|
1686
1677
|
{ messageType: type }));
|
|
1687
1678
|
return -1;
|
|
1688
1679
|
}
|
|
1689
|
-
return this.submitMessage(type, contents, batch, metadata);
|
|
1690
1680
|
}
|
|
1691
1681
|
|
|
1692
|
-
|
|
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 {
|
|
1693
1710
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1694
1711
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1695
1712
|
return -1;
|
|
@@ -1700,28 +1717,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1700
1717
|
return this._deltaManager.submit(type, contents, batch, metadata);
|
|
1701
1718
|
}
|
|
1702
1719
|
|
|
1703
|
-
private processRemoteMessage(message: ISequencedDocumentMessage)
|
|
1720
|
+
private processRemoteMessage(message: ISequencedDocumentMessage) {
|
|
1704
1721
|
const local = this.clientId === message.clientId;
|
|
1705
1722
|
|
|
1706
1723
|
// Allow the protocol handler to process the message
|
|
1707
|
-
|
|
1708
|
-
try {
|
|
1709
|
-
result = this.protocolHandler.processMessage(message, local);
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
this.close(wrapError(error, (errorMessage) =>
|
|
1712
|
-
new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1713
|
-
}
|
|
1724
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1714
1725
|
|
|
1715
|
-
//
|
|
1716
|
-
|
|
1717
|
-
this.mc.logger.sendTelemetryEvent(
|
|
1718
|
-
{ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1719
|
-
}
|
|
1720
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1721
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1722
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1723
|
-
this.context.process(message, local, undefined);
|
|
1724
|
-
}
|
|
1726
|
+
// Forward messages to the loaded runtime for processing
|
|
1727
|
+
this.context.process(message, local, undefined);
|
|
1725
1728
|
|
|
1726
1729
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1727
1730
|
if (this.activeConnection()) {
|
|
@@ -1734,10 +1737,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1734
1737
|
this.serviceConfiguration !== undefined,
|
|
1735
1738
|
0x2e4 /* "there should be service config for active connection" */);
|
|
1736
1739
|
this.collabWindowTracker = new CollabWindowTracker(
|
|
1737
|
-
(type
|
|
1740
|
+
(type) => {
|
|
1738
1741
|
assert(this.activeConnection(),
|
|
1739
1742
|
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1740
|
-
this.submitMessage(type
|
|
1743
|
+
this.submitMessage(type);
|
|
1741
1744
|
},
|
|
1742
1745
|
this.serviceConfiguration.noopTimeFrequency,
|
|
1743
1746
|
this.serviceConfiguration.noopCountFrequency,
|
|
@@ -1747,8 +1750,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1747
1750
|
}
|
|
1748
1751
|
|
|
1749
1752
|
this.emit("op", message);
|
|
1750
|
-
|
|
1751
|
-
return result;
|
|
1752
1753
|
}
|
|
1753
1754
|
|
|
1754
1755
|
private submitSignal(message: any) {
|
|
@@ -1758,14 +1759,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1758
1759
|
private processSignal(message: ISignalMessage) {
|
|
1759
1760
|
// No clientId indicates a system signal message.
|
|
1760
1761
|
if (message.clientId === null) {
|
|
1761
|
-
|
|
1762
|
-
if (innerContent.type === MessageType.ClientJoin) {
|
|
1763
|
-
const newClient = innerContent.content as ISignalClient;
|
|
1764
|
-
this._audience.addMember(newClient.clientId, newClient.client);
|
|
1765
|
-
} else if (innerContent.type === MessageType.ClientLeave) {
|
|
1766
|
-
const leftClientId = innerContent.content as string;
|
|
1767
|
-
this._audience.removeMember(leftClientId);
|
|
1768
|
-
}
|
|
1762
|
+
this.protocolHandler.processSignal(message);
|
|
1769
1763
|
} else {
|
|
1770
1764
|
const local = this.clientId === message.clientId;
|
|
1771
1765
|
this.context.processSignal(message, local);
|
|
@@ -1831,6 +1825,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1831
1825
|
new QuorumProxy(this.protocolHandler.quorum),
|
|
1832
1826
|
loader,
|
|
1833
1827
|
(type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata),
|
|
1828
|
+
(summaryOp: ISummaryContent) => this.submitSummaryMessage(summaryOp),
|
|
1829
|
+
(batch: IBatchMessage[]) => this.submitBatch(batch),
|
|
1834
1830
|
(message) => this.submitSignal(message),
|
|
1835
1831
|
(error?: ICriticalContainerError) => this.close(error),
|
|
1836
1832
|
Container.version,
|
|
@@ -1853,4 +1849,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1853
1849
|
private logContainerError(warning: ContainerWarning) {
|
|
1854
1850
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1855
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
|
+
}
|
|
1856
1870
|
}
|