@fluidframework/container-loader 1.2.6 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/dist/audience.d.ts +2 -6
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +6 -11
- 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 +43 -22
- 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 +29 -17
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +181 -171
- 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 +39 -12
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +4 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaQueue.d.ts +9 -2
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +31 -26
- 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 +27 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +79 -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 -6
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +6 -11
- 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 +44 -25
- 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 +29 -17
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +184 -174
- 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 +41 -14
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +4 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaQueue.d.ts +9 -2
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +32 -27
- 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 +27 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +75 -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 +27 -19
- package/src/audience.ts +8 -14
- package/src/catchUpMonitor.ts +59 -0
- package/src/collabWindowTracker.ts +15 -6
- package/src/connectionManager.ts +56 -33
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +235 -70
- package/src/container.ts +241 -218
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +71 -16
- package/src/contracts.ts +7 -7
- package/src/deltaManager.ts +48 -15
- package/src/deltaQueue.ts +34 -28
- package/src/index.ts +4 -0
- package/src/loader.ts +14 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +120 -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,14 +58,12 @@ import {
|
|
|
64
58
|
ICommittedProposal,
|
|
65
59
|
IDocumentAttributes,
|
|
66
60
|
IDocumentMessage,
|
|
67
|
-
IProcessMessageResult,
|
|
68
61
|
IProtocolState,
|
|
69
62
|
IQuorumClients,
|
|
70
63
|
IQuorumProposals,
|
|
71
64
|
ISequencedClient,
|
|
72
65
|
ISequencedDocumentMessage,
|
|
73
66
|
ISequencedProposal,
|
|
74
|
-
ISignalClient,
|
|
75
67
|
ISignalMessage,
|
|
76
68
|
ISnapshotTree,
|
|
77
69
|
ISummaryContent,
|
|
@@ -100,15 +92,21 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
100
92
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
101
93
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
102
94
|
import { pkgVersion } from "./packageVersion";
|
|
103
|
-
import {
|
|
104
|
-
import {
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
96
|
+
import {
|
|
97
|
+
IConnectionStateHandler,
|
|
98
|
+
createConnectionStateHandler,
|
|
99
|
+
} from "./connectionStateHandler";
|
|
107
100
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
108
101
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
109
102
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
110
103
|
import { ConnectionManager } from "./connectionManager";
|
|
111
104
|
import { ConnectionState } from "./connectionState";
|
|
105
|
+
import {
|
|
106
|
+
IProtocolHandler,
|
|
107
|
+
ProtocolHandler,
|
|
108
|
+
ProtocolHandlerBuilder,
|
|
109
|
+
} from "./protocol";
|
|
112
110
|
|
|
113
111
|
const detachedContainerRefSeqNumber = 0;
|
|
114
112
|
|
|
@@ -149,14 +147,19 @@ export interface IContainerConfig {
|
|
|
149
147
|
}
|
|
150
148
|
|
|
151
149
|
/**
|
|
152
|
-
* Waits until container connects to delta storage and gets up-to-date
|
|
150
|
+
* Waits until container connects to delta storage and gets up-to-date.
|
|
151
|
+
*
|
|
153
152
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
154
153
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
154
|
+
*
|
|
155
155
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
156
156
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
157
|
+
*
|
|
158
|
+
* @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
|
|
159
|
+
*
|
|
160
|
+
* `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
|
|
161
|
+
* but it maybe still behind.
|
|
162
|
+
*
|
|
160
163
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
161
164
|
*/
|
|
162
165
|
export async function waitContainerToCatchUp(container: IContainer) {
|
|
@@ -179,6 +182,10 @@ export async function waitContainerToCatchUp(container: IContainer) {
|
|
|
179
182
|
};
|
|
180
183
|
container.on("closed", closedCallback);
|
|
181
184
|
|
|
185
|
+
// Depending on config, transition to "connected" state may include the guarantee
|
|
186
|
+
// that all known ops have been processed. If so, we may introduce additional wait here.
|
|
187
|
+
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
188
|
+
// which is a reasonable approximation of "caught up"
|
|
182
189
|
const waitForOps = () => {
|
|
183
190
|
assert(container.connectionState === ConnectionState.CatchingUp
|
|
184
191
|
|| container.connectionState === ConnectionState.Connected,
|
|
@@ -228,6 +235,24 @@ const getCodeProposal =
|
|
|
228
235
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
229
236
|
(quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
|
|
230
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (1s)
|
|
240
|
+
* @param logger - logger to use
|
|
241
|
+
* @param eventName - event name
|
|
242
|
+
* @param action - functor to call and measure
|
|
243
|
+
*/
|
|
244
|
+
async function ReportIfTooLong(
|
|
245
|
+
logger: ITelemetryLogger,
|
|
246
|
+
eventName: string,
|
|
247
|
+
action: () => Promise<ITelemetryProperties>,
|
|
248
|
+
) {
|
|
249
|
+
const event = PerformanceEvent.start(logger, { eventName });
|
|
250
|
+
const props = await action();
|
|
251
|
+
if (event.duration > 1000) {
|
|
252
|
+
event.end(props);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
231
256
|
/**
|
|
232
257
|
* State saved by a container at close time, to be used to load a new instance
|
|
233
258
|
* of the container to the same state
|
|
@@ -252,6 +277,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
252
277
|
loader: Loader,
|
|
253
278
|
loadOptions: IContainerLoadOptions,
|
|
254
279
|
pendingLocalState?: IPendingContainerState,
|
|
280
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
255
281
|
): Promise<Container> {
|
|
256
282
|
const container = new Container(
|
|
257
283
|
loader,
|
|
@@ -260,7 +286,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
260
286
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
261
287
|
canReconnect: loadOptions.canReconnect,
|
|
262
288
|
serializedContainerState: pendingLocalState,
|
|
263
|
-
}
|
|
289
|
+
},
|
|
290
|
+
protocolHandlerBuilder);
|
|
264
291
|
|
|
265
292
|
return PerformanceEvent.timedExecAsync(
|
|
266
293
|
container.mc.logger,
|
|
@@ -308,10 +335,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
308
335
|
public static async createDetached(
|
|
309
336
|
loader: Loader,
|
|
310
337
|
codeDetails: IFluidCodeDetails,
|
|
338
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
311
339
|
): Promise<Container> {
|
|
312
340
|
const container = new Container(
|
|
313
341
|
loader,
|
|
314
|
-
{}
|
|
342
|
+
{},
|
|
343
|
+
protocolHandlerBuilder);
|
|
315
344
|
|
|
316
345
|
return PerformanceEvent.timedExecAsync(
|
|
317
346
|
container.mc.logger,
|
|
@@ -330,10 +359,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
330
359
|
public static async rehydrateDetachedFromSnapshot(
|
|
331
360
|
loader: Loader,
|
|
332
361
|
snapshot: string,
|
|
362
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
333
363
|
): Promise<Container> {
|
|
334
364
|
const container = new Container(
|
|
335
365
|
loader,
|
|
336
|
-
{}
|
|
366
|
+
{},
|
|
367
|
+
protocolHandlerBuilder);
|
|
368
|
+
|
|
337
369
|
return PerformanceEvent.timedExecAsync(
|
|
338
370
|
container.mc.logger,
|
|
339
371
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
@@ -360,7 +392,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
360
392
|
// Only transition states if currently loading
|
|
361
393
|
if (this._lifecycleState === "loading") {
|
|
362
394
|
// Propagate current connection state through the system.
|
|
363
|
-
this.propagateConnectionState();
|
|
395
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
364
396
|
this._lifecycleState = "loaded";
|
|
365
397
|
}
|
|
366
398
|
}
|
|
@@ -371,23 +403,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
371
403
|
|
|
372
404
|
private _attachState = AttachState.Detached;
|
|
373
405
|
|
|
374
|
-
private readonly
|
|
406
|
+
private readonly storageService: ContainerStorageAdapter;
|
|
375
407
|
public get storage(): IDocumentStorageService {
|
|
376
|
-
return this.
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
private _storageService: IDocumentStorageService & IDisposable | undefined;
|
|
380
|
-
private get storageService(): IDocumentStorageService {
|
|
381
|
-
if (this._storageService === undefined) {
|
|
382
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
383
|
-
}
|
|
384
|
-
return this._storageService;
|
|
408
|
+
return this.storageService;
|
|
385
409
|
}
|
|
386
410
|
|
|
387
411
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
388
412
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
389
413
|
private service: IDocumentService | undefined;
|
|
390
|
-
private readonly _audience: Audience;
|
|
391
414
|
|
|
392
415
|
private _context: ContainerContext | undefined;
|
|
393
416
|
private get context() {
|
|
@@ -416,7 +439,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
416
439
|
|
|
417
440
|
private lastVisible: number | undefined;
|
|
418
441
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
419
|
-
private readonly connectionStateHandler:
|
|
442
|
+
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
420
443
|
|
|
421
444
|
private setAutoReconnectTime = performance.now();
|
|
422
445
|
|
|
@@ -458,7 +481,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
458
481
|
}
|
|
459
482
|
|
|
460
483
|
public get connected(): boolean {
|
|
461
|
-
return this.connectionStateHandler.
|
|
484
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
462
485
|
}
|
|
463
486
|
|
|
464
487
|
/**
|
|
@@ -469,12 +492,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
469
492
|
return this._deltaManager.serviceConfiguration;
|
|
470
493
|
}
|
|
471
494
|
|
|
495
|
+
private _clientId: string | undefined;
|
|
496
|
+
|
|
472
497
|
/**
|
|
473
498
|
* The server provided id of the client.
|
|
474
499
|
* Set once this.connected is true, otherwise undefined
|
|
475
500
|
*/
|
|
476
501
|
public get clientId(): string | undefined {
|
|
477
|
-
return this.
|
|
502
|
+
return this._clientId;
|
|
478
503
|
}
|
|
479
504
|
|
|
480
505
|
/**
|
|
@@ -510,13 +535,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
510
535
|
* Retrieves the audience associated with the document
|
|
511
536
|
*/
|
|
512
537
|
public get audience(): IAudience {
|
|
513
|
-
return this.
|
|
538
|
+
return this.protocolHandler.audience;
|
|
514
539
|
}
|
|
515
540
|
|
|
516
541
|
/**
|
|
517
542
|
* Returns true if container is dirty.
|
|
518
543
|
* Which means data loss if container is closed at that same moment
|
|
519
|
-
* Most likely that happens when there is no network connection to
|
|
544
|
+
* Most likely that happens when there is no network connection to Relay Service
|
|
520
545
|
*/
|
|
521
546
|
public get isDirty() {
|
|
522
547
|
return this._dirtyContainer;
|
|
@@ -531,6 +556,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
531
556
|
constructor(
|
|
532
557
|
private readonly loader: Loader,
|
|
533
558
|
config: IContainerConfig,
|
|
559
|
+
private readonly protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
534
560
|
) {
|
|
535
561
|
super((name, error) => {
|
|
536
562
|
this.mc.logger.sendErrorEvent(
|
|
@@ -540,7 +566,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
540
566
|
},
|
|
541
567
|
error);
|
|
542
568
|
});
|
|
543
|
-
this._audience = new Audience();
|
|
544
569
|
|
|
545
570
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
546
571
|
this._resolvedUrl = config.resolvedUrl;
|
|
@@ -566,6 +591,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
566
591
|
containerAttachState: () => this._attachState,
|
|
567
592
|
containerLifecycleState: () => this._lifecycleState,
|
|
568
593
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
594
|
+
serializedContainer: config.serializedContainerState !== undefined,
|
|
569
595
|
},
|
|
570
596
|
// we need to be judicious with our logging here to avoid generating too much data
|
|
571
597
|
// all data logged here should be broadly applicable, and not specific to a
|
|
@@ -578,6 +604,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
578
604
|
containerLoadedFromVersionId: () => this.loadedFromVersion?.id,
|
|
579
605
|
containerLoadedFromVersionDate: () => this.loadedFromVersion?.date,
|
|
580
606
|
// message information to associate errors with the specific execution state
|
|
607
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
581
608
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
582
609
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
583
610
|
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
|
|
@@ -598,11 +625,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
598
625
|
summarizeProtocolTree,
|
|
599
626
|
};
|
|
600
627
|
|
|
601
|
-
this.
|
|
628
|
+
this._deltaManager = this.createDeltaManager();
|
|
629
|
+
|
|
630
|
+
this._clientId = config.serializedContainerState?.clientId;
|
|
631
|
+
this.connectionStateHandler = createConnectionStateHandler(
|
|
602
632
|
{
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
633
|
+
logger: this.mc.logger,
|
|
634
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
635
|
+
if (value === ConnectionState.Connected) {
|
|
636
|
+
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
637
|
+
}
|
|
638
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
639
|
+
if (this._lifecycleState === "loaded") {
|
|
640
|
+
this.propagateConnectionState(false /* initial transition */);
|
|
641
|
+
}
|
|
642
|
+
},
|
|
606
643
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
607
644
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
608
645
|
logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
|
|
@@ -614,35 +651,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
614
651
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
615
652
|
});
|
|
616
653
|
},
|
|
617
|
-
connectionStateChanged: () => {
|
|
618
|
-
// Fire events only if container is fully loaded and not closed
|
|
619
|
-
if (this._lifecycleState === "loaded") {
|
|
620
|
-
this.propagateConnectionState();
|
|
621
|
-
}
|
|
622
|
-
},
|
|
623
654
|
},
|
|
624
|
-
this.
|
|
625
|
-
|
|
655
|
+
this.deltaManager,
|
|
656
|
+
this._clientId,
|
|
626
657
|
);
|
|
627
658
|
|
|
628
659
|
this.on(savedContainerEvent, () => {
|
|
629
660
|
this.connectionStateHandler.containerSaved();
|
|
630
661
|
});
|
|
631
662
|
|
|
632
|
-
this.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
this.mc.logger.sendErrorEvent({
|
|
640
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
641
|
-
});
|
|
642
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
643
|
-
}
|
|
644
|
-
return this.storageService;
|
|
645
|
-
},
|
|
663
|
+
this.storageService = new ContainerStorageAdapter(
|
|
664
|
+
this.loader.services.detachedBlobStorage,
|
|
665
|
+
this.mc.logger,
|
|
666
|
+
this.options.summarizeProtocolTree === true
|
|
667
|
+
? () => this.captureProtocolSummary()
|
|
668
|
+
: undefined,
|
|
646
669
|
);
|
|
647
670
|
|
|
648
671
|
const isDomAvailable = typeof document === "object" &&
|
|
@@ -742,7 +765,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
742
765
|
|
|
743
766
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
744
767
|
|
|
745
|
-
this.
|
|
768
|
+
this.storageService.dispose();
|
|
746
769
|
|
|
747
770
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
748
771
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -767,13 +790,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
767
790
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
768
791
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
769
792
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
770
|
-
|
|
771
793
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
772
794
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
773
795
|
0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
774
796
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
775
797
|
assert(this._protocolHandler.attributes.term !== undefined,
|
|
776
|
-
|
|
798
|
+
0x37e /* Must have a valid protocol handler instance */);
|
|
777
799
|
const pendingState: IPendingContainerState = {
|
|
778
800
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
779
801
|
url: this.resolvedUrl.url,
|
|
@@ -782,6 +804,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
782
804
|
clientId: this.clientId,
|
|
783
805
|
};
|
|
784
806
|
|
|
807
|
+
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
808
|
+
|
|
785
809
|
this.close();
|
|
786
810
|
|
|
787
811
|
return JSON.stringify(pendingState);
|
|
@@ -863,7 +887,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
863
887
|
const resolvedUrl = this.service.resolvedUrl;
|
|
864
888
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
865
889
|
this._resolvedUrl = resolvedUrl;
|
|
866
|
-
await this.
|
|
890
|
+
await this.storageService.connectToService(this.service);
|
|
867
891
|
|
|
868
892
|
if (hasAttachmentBlobs) {
|
|
869
893
|
// upload blobs to storage
|
|
@@ -901,8 +925,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
901
925
|
this._attachState = AttachState.Attached;
|
|
902
926
|
this.emit("attached");
|
|
903
927
|
|
|
904
|
-
// Propagate current connection state through the system.
|
|
905
|
-
this.propagateConnectionState();
|
|
906
928
|
if (!this.closed) {
|
|
907
929
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
908
930
|
}
|
|
@@ -1078,9 +1100,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1078
1100
|
/**
|
|
1079
1101
|
* Load container.
|
|
1080
1102
|
*
|
|
1081
|
-
* @param specifiedVersion -
|
|
1082
|
-
* - undefined - fetch latest snapshot
|
|
1083
|
-
* - otherwise, version sha to load snapshot
|
|
1103
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
1084
1104
|
*/
|
|
1085
1105
|
private async load(
|
|
1086
1106
|
specifiedVersion: string | undefined,
|
|
@@ -1114,10 +1134,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1114
1134
|
}
|
|
1115
1135
|
|
|
1116
1136
|
if (!pendingLocalState) {
|
|
1117
|
-
await this.
|
|
1137
|
+
await this.storageService.connectToService(this.service);
|
|
1118
1138
|
} else {
|
|
1119
1139
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1120
|
-
this.
|
|
1140
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
1121
1141
|
}
|
|
1122
1142
|
|
|
1123
1143
|
this._attachState = AttachState.Attached;
|
|
@@ -1158,14 +1178,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1158
1178
|
|
|
1159
1179
|
// ...load in the existing quorum
|
|
1160
1180
|
// Initialize the protocol handler
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1181
|
+
if (pendingLocalState === undefined) {
|
|
1182
|
+
await this.initializeProtocolStateFromSnapshot(
|
|
1183
|
+
attributes,
|
|
1184
|
+
this.storageService,
|
|
1185
|
+
snapshot);
|
|
1186
|
+
} else {
|
|
1187
|
+
this.initializeProtocolState(
|
|
1164
1188
|
attributes,
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1189
|
+
{
|
|
1190
|
+
members: pendingLocalState.protocol.members,
|
|
1191
|
+
proposals: pendingLocalState.protocol.proposals,
|
|
1192
|
+
values: pendingLocalState.protocol.values,
|
|
1193
|
+
}, // pending IQuorumSnapshot
|
|
1168
1194
|
);
|
|
1195
|
+
}
|
|
1169
1196
|
|
|
1170
1197
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1171
1198
|
await this.instantiateContext(
|
|
@@ -1175,17 +1202,20 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1175
1202
|
pendingLocalState?.pendingRuntimeState,
|
|
1176
1203
|
);
|
|
1177
1204
|
|
|
1178
|
-
// Internal context is fully loaded at this point
|
|
1179
|
-
this.setLoaded();
|
|
1180
|
-
|
|
1181
1205
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
1182
1206
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
1183
1207
|
if (!this.closed) {
|
|
1184
1208
|
if (opsBeforeReturnP !== undefined) {
|
|
1185
1209
|
this._deltaManager.inbound.resume();
|
|
1186
1210
|
|
|
1187
|
-
await
|
|
1188
|
-
|
|
1211
|
+
await ReportIfTooLong(
|
|
1212
|
+
this.mc.logger,
|
|
1213
|
+
"WaitOps",
|
|
1214
|
+
async () => { await opsBeforeReturnP; return {}; });
|
|
1215
|
+
await ReportIfTooLong(
|
|
1216
|
+
this.mc.logger,
|
|
1217
|
+
"WaitOpProcessing",
|
|
1218
|
+
async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
1189
1219
|
|
|
1190
1220
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1191
1221
|
this._deltaManager.inbound.pause();
|
|
@@ -1215,9 +1245,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1215
1245
|
throw new Error("Container was closed while load()");
|
|
1216
1246
|
}
|
|
1217
1247
|
|
|
1248
|
+
// Internal context is fully loaded at this point
|
|
1249
|
+
this.setLoaded();
|
|
1250
|
+
|
|
1218
1251
|
return {
|
|
1219
1252
|
sequenceNumber: attributes.sequenceNumber,
|
|
1220
1253
|
version: versionId,
|
|
1254
|
+
dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
|
|
1255
|
+
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
1221
1256
|
};
|
|
1222
1257
|
}
|
|
1223
1258
|
|
|
@@ -1232,11 +1267,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1232
1267
|
|
|
1233
1268
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1234
1269
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
1235
|
-
this.
|
|
1270
|
+
this.initializeProtocolState(
|
|
1236
1271
|
attributes,
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1272
|
+
{
|
|
1273
|
+
members: [],
|
|
1274
|
+
proposals: [],
|
|
1275
|
+
values: qValues,
|
|
1276
|
+
}, // IQuorumSnapShot
|
|
1240
1277
|
);
|
|
1241
1278
|
|
|
1242
1279
|
// The load context - given we seeded the quorum - will be great
|
|
@@ -1255,24 +1292,26 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1255
1292
|
}
|
|
1256
1293
|
|
|
1257
1294
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1258
|
-
this.
|
|
1259
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1295
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1296
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
1260
1297
|
|
|
1261
1298
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1262
1299
|
|
|
1263
1300
|
// Initialize the protocol handler
|
|
1264
1301
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1265
1302
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1266
|
-
this.
|
|
1303
|
+
this.storageService,
|
|
1267
1304
|
baseTree.blobs.quorumValues,
|
|
1268
1305
|
);
|
|
1269
1306
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1270
|
-
this.
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
[],
|
|
1274
|
-
[],
|
|
1275
|
-
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []
|
|
1307
|
+
this.initializeProtocolState(
|
|
1308
|
+
attributes,
|
|
1309
|
+
{
|
|
1310
|
+
members: [],
|
|
1311
|
+
proposals: [],
|
|
1312
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1313
|
+
}, // IQuorumSnapShot
|
|
1314
|
+
);
|
|
1276
1315
|
|
|
1277
1316
|
await this.instantiateContextDetached(
|
|
1278
1317
|
true, // existing
|
|
@@ -1282,28 +1321,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1282
1321
|
this.setLoaded();
|
|
1283
1322
|
}
|
|
1284
1323
|
|
|
1285
|
-
private async connectStorageService(): Promise<void> {
|
|
1286
|
-
if (this._storageService !== undefined) {
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
1291
|
-
const storageService = await this.service.connectToStorage();
|
|
1292
|
-
|
|
1293
|
-
this._storageService =
|
|
1294
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
1295
|
-
|
|
1296
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
1297
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
1298
|
-
this._storageService =
|
|
1299
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
1303
|
-
assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
|
|
1304
|
-
0x0e0 /* "lost minBlobSize policy" */);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
1324
|
private async getDocumentAttributes(
|
|
1308
1325
|
storage: IDocumentStorageService,
|
|
1309
1326
|
tree: ISnapshotTree | undefined,
|
|
@@ -1335,43 +1352,35 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1335
1352
|
attributes: IDocumentAttributes,
|
|
1336
1353
|
storage: IDocumentStorageService,
|
|
1337
1354
|
snapshot: ISnapshotTree | undefined,
|
|
1338
|
-
): Promise<
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1355
|
+
): Promise<void> {
|
|
1356
|
+
const quorumSnapshot: IQuorumSnapshot = {
|
|
1357
|
+
members: [],
|
|
1358
|
+
proposals: [],
|
|
1359
|
+
values: [],
|
|
1360
|
+
};
|
|
1342
1361
|
|
|
1343
1362
|
if (snapshot !== undefined) {
|
|
1344
1363
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
1345
|
-
[members, proposals, values] = await Promise.all([
|
|
1364
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
|
|
1346
1365
|
readAndParse<[string, ISequencedClient][]>(storage, baseTree.blobs.quorumMembers),
|
|
1347
1366
|
readAndParse<[number, ISequencedProposal, string[]][]>(storage, baseTree.blobs.quorumProposals),
|
|
1348
1367
|
readAndParse<[string, ICommittedProposal][]>(storage, baseTree.blobs.quorumValues),
|
|
1349
1368
|
]);
|
|
1350
1369
|
}
|
|
1351
1370
|
|
|
1352
|
-
|
|
1353
|
-
attributes,
|
|
1354
|
-
members,
|
|
1355
|
-
proposals,
|
|
1356
|
-
values);
|
|
1357
|
-
|
|
1358
|
-
return protocolHandler;
|
|
1371
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1359
1372
|
}
|
|
1360
1373
|
|
|
1361
|
-
private
|
|
1374
|
+
private initializeProtocolState(
|
|
1362
1375
|
attributes: IDocumentAttributes,
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const protocol =
|
|
1368
|
-
attributes
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
members,
|
|
1372
|
-
proposals,
|
|
1373
|
-
values,
|
|
1374
|
-
(key, value) => this.submitMessage(MessageType.Propose, { key, value }),
|
|
1376
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
1377
|
+
): void {
|
|
1378
|
+
const protocolHandlerBuilder =
|
|
1379
|
+
this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1380
|
+
const protocol = protocolHandlerBuilder(
|
|
1381
|
+
attributes,
|
|
1382
|
+
quorumSnapshot,
|
|
1383
|
+
(key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1375
1384
|
);
|
|
1376
1385
|
|
|
1377
1386
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
@@ -1404,8 +1413,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1404
1413
|
});
|
|
1405
1414
|
}
|
|
1406
1415
|
});
|
|
1407
|
-
|
|
1408
|
-
|
|
1416
|
+
// we need to make sure this member get set in a synchronous context,
|
|
1417
|
+
// or other things can happen after the object that will be set is created, but not yet set
|
|
1418
|
+
// this was breaking this._initialClients handling
|
|
1419
|
+
//
|
|
1420
|
+
this._protocolHandler = protocol;
|
|
1409
1421
|
}
|
|
1410
1422
|
|
|
1411
1423
|
private captureProtocolSummary(): ISummaryTree {
|
|
@@ -1494,14 +1506,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1494
1506
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1495
1507
|
deltaManager.inboundSignal.pause();
|
|
1496
1508
|
|
|
1497
|
-
deltaManager.on("connect", (details: IConnectionDetails,
|
|
1498
|
-
// Back-compat for new client and old server.
|
|
1499
|
-
this._audience.clear();
|
|
1500
|
-
|
|
1501
|
-
for (const priorClient of details.initialClients ?? []) {
|
|
1502
|
-
this._audience.addMember(priorClient.clientId, priorClient.client);
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1509
|
+
deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
|
|
1505
1510
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1506
1511
|
this.connectionMode,
|
|
1507
1512
|
details,
|
|
@@ -1524,6 +1529,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1524
1529
|
});
|
|
1525
1530
|
|
|
1526
1531
|
deltaManager.on("readonly", (readonly) => {
|
|
1532
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1527
1533
|
this.emit("readonly", readonly);
|
|
1528
1534
|
});
|
|
1529
1535
|
|
|
@@ -1578,11 +1584,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1578
1584
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1579
1585
|
}
|
|
1580
1586
|
}
|
|
1581
|
-
|
|
1582
|
-
connectionInitiationReason = "InitialConnect";
|
|
1583
|
-
} else {
|
|
1584
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1585
|
-
}
|
|
1587
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1586
1588
|
}
|
|
1587
1589
|
|
|
1588
1590
|
this.mc.logger.sendPerformanceEvent({
|
|
@@ -1608,7 +1610,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1608
1610
|
}
|
|
1609
1611
|
}
|
|
1610
1612
|
|
|
1611
|
-
private propagateConnectionState() {
|
|
1613
|
+
private propagateConnectionState(initialTransition: boolean) {
|
|
1614
|
+
// When container loaded, we want to propagate initial connection state.
|
|
1615
|
+
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1616
|
+
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1617
|
+
if (!initialTransition &&
|
|
1618
|
+
this.connectionState !== ConnectionState.Connected &&
|
|
1619
|
+
this.connectionState !== ConnectionState.Disconnected) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const state = this.connectionState === ConnectionState.Connected;
|
|
1623
|
+
|
|
1612
1624
|
const logOpsOnReconnect: boolean =
|
|
1613
1625
|
this.connectionState === ConnectionState.Connected &&
|
|
1614
1626
|
!this.firstConnection &&
|
|
@@ -1617,13 +1629,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1617
1629
|
this.messageCountAfterDisconnection = 0;
|
|
1618
1630
|
}
|
|
1619
1631
|
|
|
1620
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1621
|
-
|
|
1622
1632
|
// Both protocol and context should not be undefined if we got so far.
|
|
1623
1633
|
|
|
1624
|
-
|
|
1625
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1626
|
-
}
|
|
1634
|
+
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
1627
1635
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1628
1636
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1629
1637
|
|
|
@@ -1633,35 +1641,53 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1633
1641
|
}
|
|
1634
1642
|
}
|
|
1635
1643
|
|
|
1644
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1636
1645
|
private submitContainerMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
|
|
1637
|
-
|
|
1638
|
-
switch (outboundMessageType) {
|
|
1646
|
+
switch (type) {
|
|
1639
1647
|
case MessageType.Operation:
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
const summary = contents as ISummaryContent;
|
|
1648
|
-
if (summary.details === undefined) {
|
|
1649
|
-
summary.details = {};
|
|
1650
|
-
}
|
|
1651
|
-
summary.details.includesProtocolTree =
|
|
1652
|
-
this.options.summarizeProtocolTree === true;
|
|
1653
|
-
break;
|
|
1654
|
-
}
|
|
1648
|
+
return this.submitMessage(
|
|
1649
|
+
type,
|
|
1650
|
+
JSON.stringify(contents),
|
|
1651
|
+
batch,
|
|
1652
|
+
metadata);
|
|
1653
|
+
case MessageType.Summarize:
|
|
1654
|
+
return this.submitSummaryMessage(contents as unknown as ISummaryContent);
|
|
1655
1655
|
default:
|
|
1656
1656
|
this.close(new GenericError("invalidContainerSubmitOpType",
|
|
1657
1657
|
undefined /* error */,
|
|
1658
1658
|
{ messageType: type }));
|
|
1659
1659
|
return -1;
|
|
1660
1660
|
}
|
|
1661
|
-
return this.submitMessage(type, contents, batch, metadata);
|
|
1662
1661
|
}
|
|
1663
1662
|
|
|
1664
|
-
|
|
1663
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
1664
|
+
private submitBatch(batch: IBatchMessage[]): number {
|
|
1665
|
+
let clientSequenceNumber = -1;
|
|
1666
|
+
for (const message of batch) {
|
|
1667
|
+
clientSequenceNumber = this.submitMessage(
|
|
1668
|
+
MessageType.Operation,
|
|
1669
|
+
message.contents,
|
|
1670
|
+
true, // batch
|
|
1671
|
+
message.metadata);
|
|
1672
|
+
}
|
|
1673
|
+
this._deltaManager.flush();
|
|
1674
|
+
return clientSequenceNumber;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
private submitSummaryMessage(summary: ISummaryContent) {
|
|
1678
|
+
// github #6451: this is only needed for staging so the server
|
|
1679
|
+
// know when the protocol tree is included
|
|
1680
|
+
// this can be removed once all clients send
|
|
1681
|
+
// protocol tree by default
|
|
1682
|
+
if (summary.details === undefined) {
|
|
1683
|
+
summary.details = {};
|
|
1684
|
+
}
|
|
1685
|
+
summary.details.includesProtocolTree =
|
|
1686
|
+
this.options.summarizeProtocolTree === true;
|
|
1687
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
private submitMessage(type: MessageType, contents?: string, batch?: boolean, metadata?: any): number {
|
|
1665
1691
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1666
1692
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1667
1693
|
return -1;
|
|
@@ -1672,28 +1698,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1672
1698
|
return this._deltaManager.submit(type, contents, batch, metadata);
|
|
1673
1699
|
}
|
|
1674
1700
|
|
|
1675
|
-
private processRemoteMessage(message: ISequencedDocumentMessage)
|
|
1701
|
+
private processRemoteMessage(message: ISequencedDocumentMessage) {
|
|
1676
1702
|
const local = this.clientId === message.clientId;
|
|
1677
1703
|
|
|
1678
1704
|
// Allow the protocol handler to process the message
|
|
1679
|
-
|
|
1680
|
-
try {
|
|
1681
|
-
result = this.protocolHandler.processMessage(message, local);
|
|
1682
|
-
} catch (error) {
|
|
1683
|
-
this.close(wrapError(error, (errorMessage) =>
|
|
1684
|
-
new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1685
|
-
}
|
|
1705
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1686
1706
|
|
|
1687
|
-
//
|
|
1688
|
-
|
|
1689
|
-
this.mc.logger.sendTelemetryEvent(
|
|
1690
|
-
{ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1691
|
-
}
|
|
1692
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1693
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1694
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1695
|
-
this.context.process(message, local, undefined);
|
|
1696
|
-
}
|
|
1707
|
+
// Forward messages to the loaded runtime for processing
|
|
1708
|
+
this.context.process(message, local, undefined);
|
|
1697
1709
|
|
|
1698
1710
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1699
1711
|
if (this.activeConnection()) {
|
|
@@ -1706,10 +1718,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1706
1718
|
this.serviceConfiguration !== undefined,
|
|
1707
1719
|
0x2e4 /* "there should be service config for active connection" */);
|
|
1708
1720
|
this.collabWindowTracker = new CollabWindowTracker(
|
|
1709
|
-
(type
|
|
1721
|
+
(type) => {
|
|
1710
1722
|
assert(this.activeConnection(),
|
|
1711
1723
|
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1712
|
-
this.submitMessage(type
|
|
1724
|
+
this.submitMessage(type);
|
|
1713
1725
|
},
|
|
1714
1726
|
this.serviceConfiguration.noopTimeFrequency,
|
|
1715
1727
|
this.serviceConfiguration.noopCountFrequency,
|
|
@@ -1719,8 +1731,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1719
1731
|
}
|
|
1720
1732
|
|
|
1721
1733
|
this.emit("op", message);
|
|
1722
|
-
|
|
1723
|
-
return result;
|
|
1724
1734
|
}
|
|
1725
1735
|
|
|
1726
1736
|
private submitSignal(message: any) {
|
|
@@ -1730,14 +1740,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1730
1740
|
private processSignal(message: ISignalMessage) {
|
|
1731
1741
|
// No clientId indicates a system signal message.
|
|
1732
1742
|
if (message.clientId === null) {
|
|
1733
|
-
|
|
1734
|
-
if (innerContent.type === MessageType.ClientJoin) {
|
|
1735
|
-
const newClient = innerContent.content as ISignalClient;
|
|
1736
|
-
this._audience.addMember(newClient.clientId, newClient.client);
|
|
1737
|
-
} else if (innerContent.type === MessageType.ClientLeave) {
|
|
1738
|
-
const leftClientId = innerContent.content as string;
|
|
1739
|
-
this._audience.removeMember(leftClientId);
|
|
1740
|
-
}
|
|
1743
|
+
this.protocolHandler.processSignal(message);
|
|
1741
1744
|
} else {
|
|
1742
1745
|
const local = this.clientId === message.clientId;
|
|
1743
1746
|
this.context.processSignal(message, local);
|
|
@@ -1803,6 +1806,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1803
1806
|
new QuorumProxy(this.protocolHandler.quorum),
|
|
1804
1807
|
loader,
|
|
1805
1808
|
(type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata),
|
|
1809
|
+
(summaryOp: ISummaryContent) => this.submitSummaryMessage(summaryOp),
|
|
1810
|
+
(batch: IBatchMessage[]) => this.submitBatch(batch),
|
|
1806
1811
|
(message) => this.submitSignal(message),
|
|
1807
1812
|
(error?: ICriticalContainerError) => this.close(error),
|
|
1808
1813
|
Container.version,
|
|
@@ -1825,4 +1830,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1825
1830
|
private logContainerError(warning: ContainerWarning) {
|
|
1826
1831
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1827
1832
|
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Set the connected state of the ContainerContext
|
|
1836
|
+
* This controls the "connected" state of the ContainerRuntime as well
|
|
1837
|
+
* @param state - Is the container currently connected?
|
|
1838
|
+
* @param readonly - Is the container in readonly mode?
|
|
1839
|
+
*/
|
|
1840
|
+
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
1841
|
+
if (this._context?.disposed === false) {
|
|
1842
|
+
/**
|
|
1843
|
+
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1844
|
+
* ops getting through to the DeltaManager.
|
|
1845
|
+
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1846
|
+
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1847
|
+
*/
|
|
1848
|
+
this.context.setConnectionState(state && !readonly, this.clientId);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1828
1851
|
}
|