@fluidframework/container-loader 1.2.2 → 2.0.0-internal.1.0.0.82159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +40 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/catchUpMonitor.js +74 -0
- package/dist/catchUpMonitor.js.map +1 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +0 -1
- 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 +12 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +47 -15
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +8 -6
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +82 -46
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +2 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +2 -2
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +6 -6
- 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 +7 -0
- 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 +52 -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 +40 -0
- package/lib/catchUpMonitor.d.ts.map +1 -0
- package/lib/catchUpMonitor.js +69 -0
- package/lib/catchUpMonitor.js.map +1 -0
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +0 -1
- 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 +12 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +47 -15
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +8 -6
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +82 -46
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +2 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -2
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +6 -6
- 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 +7 -0
- 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 +48 -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 +23 -15
- package/src/audience.ts +2 -2
- package/src/catchUpMonitor.ts +99 -0
- package/src/connectionManager.ts +0 -1
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +55 -15
- package/src/container.ts +115 -63
- package/src/containerStorageAdapter.ts +8 -2
- package/src/deltaManager.ts +6 -4
- package/src/deltaQueue.ts +34 -28
- package/src/index.ts +4 -0
- package/src/loader.ts +13 -2
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +96 -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
|
-
IDisposable, ITelemetryProperties,
|
|
10
|
+
IDisposable, ITelemetryLogger, ITelemetryProperties,
|
|
11
11
|
} from "@fluidframework/common-definitions";
|
|
12
12
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
13
13
|
import {
|
|
@@ -53,10 +53,7 @@ import {
|
|
|
53
53
|
isRuntimeMessage,
|
|
54
54
|
isUnpackedRuntimeMessage,
|
|
55
55
|
} from "@fluidframework/driver-utils";
|
|
56
|
-
import {
|
|
57
|
-
IProtocolHandler,
|
|
58
|
-
ProtocolOpHandlerWithClientValidation,
|
|
59
|
-
} from "@fluidframework/protocol-base";
|
|
56
|
+
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
60
57
|
import {
|
|
61
58
|
IClient,
|
|
62
59
|
IClientConfiguration,
|
|
@@ -109,6 +106,11 @@ import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, Quorum
|
|
|
109
106
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
110
107
|
import { ConnectionManager } from "./connectionManager";
|
|
111
108
|
import { ConnectionState } from "./connectionState";
|
|
109
|
+
import {
|
|
110
|
+
IProtocolHandler,
|
|
111
|
+
ProtocolHandler,
|
|
112
|
+
ProtocolHandlerBuilder,
|
|
113
|
+
} from "./protocol";
|
|
112
114
|
|
|
113
115
|
const detachedContainerRefSeqNumber = 0;
|
|
114
116
|
|
|
@@ -179,6 +181,10 @@ export async function waitContainerToCatchUp(container: IContainer) {
|
|
|
179
181
|
};
|
|
180
182
|
container.on("closed", closedCallback);
|
|
181
183
|
|
|
184
|
+
// Depending on config, transition to "connected" state may include the guarantee
|
|
185
|
+
// that all known ops have been processed. If so, we may introduce additional wait here.
|
|
186
|
+
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
187
|
+
// which is a reasonable approximation of "caught up"
|
|
182
188
|
const waitForOps = () => {
|
|
183
189
|
assert(container.connectionState === ConnectionState.CatchingUp
|
|
184
190
|
|| container.connectionState === ConnectionState.Connected,
|
|
@@ -228,6 +234,24 @@ const getCodeProposal =
|
|
|
228
234
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
229
235
|
(quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
|
|
230
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (1s)
|
|
239
|
+
* @param logger - logger to use
|
|
240
|
+
* @param eventName - event name
|
|
241
|
+
* @param action - functor to call and measure
|
|
242
|
+
*/
|
|
243
|
+
async function ReportIfTooLong(
|
|
244
|
+
logger: ITelemetryLogger,
|
|
245
|
+
eventName: string,
|
|
246
|
+
action: () => Promise<ITelemetryProperties>,
|
|
247
|
+
) {
|
|
248
|
+
const event = PerformanceEvent.start(logger, { eventName });
|
|
249
|
+
const props = await action();
|
|
250
|
+
if (event.duration > 1000) {
|
|
251
|
+
event.end(props);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
231
255
|
/**
|
|
232
256
|
* State saved by a container at close time, to be used to load a new instance
|
|
233
257
|
* of the container to the same state
|
|
@@ -252,6 +276,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
252
276
|
loader: Loader,
|
|
253
277
|
loadOptions: IContainerLoadOptions,
|
|
254
278
|
pendingLocalState?: IPendingContainerState,
|
|
279
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
255
280
|
): Promise<Container> {
|
|
256
281
|
const container = new Container(
|
|
257
282
|
loader,
|
|
@@ -260,7 +285,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
260
285
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
261
286
|
canReconnect: loadOptions.canReconnect,
|
|
262
287
|
serializedContainerState: pendingLocalState,
|
|
263
|
-
}
|
|
288
|
+
},
|
|
289
|
+
protocolHandlerBuilder);
|
|
264
290
|
|
|
265
291
|
return PerformanceEvent.timedExecAsync(
|
|
266
292
|
container.mc.logger,
|
|
@@ -308,10 +334,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
308
334
|
public static async createDetached(
|
|
309
335
|
loader: Loader,
|
|
310
336
|
codeDetails: IFluidCodeDetails,
|
|
337
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
311
338
|
): Promise<Container> {
|
|
312
339
|
const container = new Container(
|
|
313
340
|
loader,
|
|
314
|
-
{}
|
|
341
|
+
{},
|
|
342
|
+
protocolHandlerBuilder);
|
|
315
343
|
|
|
316
344
|
return PerformanceEvent.timedExecAsync(
|
|
317
345
|
container.mc.logger,
|
|
@@ -330,10 +358,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
330
358
|
public static async rehydrateDetachedFromSnapshot(
|
|
331
359
|
loader: Loader,
|
|
332
360
|
snapshot: string,
|
|
361
|
+
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
333
362
|
): Promise<Container> {
|
|
334
363
|
const container = new Container(
|
|
335
364
|
loader,
|
|
336
|
-
{}
|
|
365
|
+
{},
|
|
366
|
+
protocolHandlerBuilder);
|
|
367
|
+
|
|
337
368
|
return PerformanceEvent.timedExecAsync(
|
|
338
369
|
container.mc.logger,
|
|
339
370
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
@@ -387,7 +418,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
387
418
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
388
419
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
389
420
|
private service: IDocumentService | undefined;
|
|
390
|
-
private
|
|
421
|
+
private _initialClients: ISignalClient[] | undefined;
|
|
391
422
|
|
|
392
423
|
private _context: ContainerContext | undefined;
|
|
393
424
|
private get context() {
|
|
@@ -510,13 +541,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
510
541
|
* Retrieves the audience associated with the document
|
|
511
542
|
*/
|
|
512
543
|
public get audience(): IAudience {
|
|
513
|
-
return this.
|
|
544
|
+
return this.protocolHandler.audience;
|
|
514
545
|
}
|
|
515
546
|
|
|
516
547
|
/**
|
|
517
548
|
* Returns true if container is dirty.
|
|
518
549
|
* Which means data loss if container is closed at that same moment
|
|
519
|
-
* Most likely that happens when there is no network connection to
|
|
550
|
+
* Most likely that happens when there is no network connection to Relay Service
|
|
520
551
|
*/
|
|
521
552
|
public get isDirty() {
|
|
522
553
|
return this._dirtyContainer;
|
|
@@ -531,6 +562,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
531
562
|
constructor(
|
|
532
563
|
private readonly loader: Loader,
|
|
533
564
|
config: IContainerConfig,
|
|
565
|
+
private readonly protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
534
566
|
) {
|
|
535
567
|
super((name, error) => {
|
|
536
568
|
this.mc.logger.sendErrorEvent(
|
|
@@ -540,7 +572,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
540
572
|
},
|
|
541
573
|
error);
|
|
542
574
|
});
|
|
543
|
-
this._audience = new Audience();
|
|
544
575
|
|
|
545
576
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
546
577
|
this._resolvedUrl = config.resolvedUrl;
|
|
@@ -566,6 +597,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
566
597
|
containerAttachState: () => this._attachState,
|
|
567
598
|
containerLifecycleState: () => this._lifecycleState,
|
|
568
599
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
600
|
+
serializedContainer: config.serializedContainerState !== undefined,
|
|
569
601
|
},
|
|
570
602
|
// we need to be judicious with our logging here to avoid generating too much data
|
|
571
603
|
// all data logged here should be broadly applicable, and not specific to a
|
|
@@ -578,6 +610,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
578
610
|
containerLoadedFromVersionId: () => this.loadedFromVersion?.id,
|
|
579
611
|
containerLoadedFromVersionDate: () => this.loadedFromVersion?.date,
|
|
580
612
|
// message information to associate errors with the specific execution state
|
|
613
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
581
614
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
582
615
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
583
616
|
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
|
|
@@ -772,8 +805,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
772
805
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
773
806
|
0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
774
807
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
775
|
-
assert(this._protocolHandler.attributes.term !== undefined,
|
|
776
|
-
0x30b /* Must have a valid protocol handler instance */);
|
|
808
|
+
assert(this._protocolHandler.attributes.term !== undefined, "Must have a valid protocol handler instance");
|
|
777
809
|
const pendingState: IPendingContainerState = {
|
|
778
810
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
779
811
|
url: this.resolvedUrl.url,
|
|
@@ -1159,12 +1191,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1159
1191
|
// ...load in the existing quorum
|
|
1160
1192
|
// Initialize the protocol handler
|
|
1161
1193
|
this._protocolHandler = pendingLocalState === undefined
|
|
1162
|
-
? await this.initializeProtocolStateFromSnapshot(
|
|
1163
|
-
|
|
1194
|
+
? await this.initializeProtocolStateFromSnapshot(
|
|
1195
|
+
attributes,
|
|
1196
|
+
this.storageService,
|
|
1197
|
+
snapshot,
|
|
1198
|
+
) : await this.initializeProtocolState(
|
|
1164
1199
|
attributes,
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1200
|
+
{
|
|
1201
|
+
members: pendingLocalState.protocol.members,
|
|
1202
|
+
proposals: pendingLocalState.protocol.proposals,
|
|
1203
|
+
values: pendingLocalState.protocol.values,
|
|
1204
|
+
}, // pending IQuorumSnapshot
|
|
1168
1205
|
);
|
|
1169
1206
|
|
|
1170
1207
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
@@ -1175,17 +1212,20 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1175
1212
|
pendingLocalState?.pendingRuntimeState,
|
|
1176
1213
|
);
|
|
1177
1214
|
|
|
1178
|
-
// Internal context is fully loaded at this point
|
|
1179
|
-
this.setLoaded();
|
|
1180
|
-
|
|
1181
1215
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
1182
1216
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
1183
1217
|
if (!this.closed) {
|
|
1184
1218
|
if (opsBeforeReturnP !== undefined) {
|
|
1185
1219
|
this._deltaManager.inbound.resume();
|
|
1186
1220
|
|
|
1187
|
-
await
|
|
1188
|
-
|
|
1221
|
+
await ReportIfTooLong(
|
|
1222
|
+
this.mc.logger,
|
|
1223
|
+
"WaitOps",
|
|
1224
|
+
async () => { await opsBeforeReturnP; return {}; });
|
|
1225
|
+
await ReportIfTooLong(
|
|
1226
|
+
this.mc.logger,
|
|
1227
|
+
"WaitOpProcessing",
|
|
1228
|
+
async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
1189
1229
|
|
|
1190
1230
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1191
1231
|
this._deltaManager.inbound.pause();
|
|
@@ -1215,9 +1255,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1215
1255
|
throw new Error("Container was closed while load()");
|
|
1216
1256
|
}
|
|
1217
1257
|
|
|
1258
|
+
// Internal context is fully loaded at this point
|
|
1259
|
+
this.setLoaded();
|
|
1260
|
+
|
|
1218
1261
|
return {
|
|
1219
1262
|
sequenceNumber: attributes.sequenceNumber,
|
|
1220
1263
|
version: versionId,
|
|
1264
|
+
dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
|
|
1265
|
+
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
1221
1266
|
};
|
|
1222
1267
|
}
|
|
1223
1268
|
|
|
@@ -1234,9 +1279,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1234
1279
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
1235
1280
|
this._protocolHandler = await this.initializeProtocolState(
|
|
1236
1281
|
attributes,
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1282
|
+
{
|
|
1283
|
+
members: [],
|
|
1284
|
+
proposals: [],
|
|
1285
|
+
values: qValues,
|
|
1286
|
+
}, // IQuorumSnapShot
|
|
1240
1287
|
);
|
|
1241
1288
|
|
|
1242
1289
|
// The load context - given we seeded the quorum - will be great
|
|
@@ -1270,9 +1317,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1270
1317
|
this._protocolHandler =
|
|
1271
1318
|
await this.initializeProtocolState(
|
|
1272
1319
|
attributes,
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1320
|
+
{
|
|
1321
|
+
members: [],
|
|
1322
|
+
proposals: [],
|
|
1323
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1324
|
+
}, // IQuorumSnapShot
|
|
1325
|
+
);
|
|
1276
1326
|
|
|
1277
1327
|
await this.instantiateContextDetached(
|
|
1278
1328
|
true, // existing
|
|
@@ -1336,44 +1386,40 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1336
1386
|
storage: IDocumentStorageService,
|
|
1337
1387
|
snapshot: ISnapshotTree | undefined,
|
|
1338
1388
|
): Promise<IProtocolHandler> {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1389
|
+
const quorumSnapshot: IQuorumSnapshot = {
|
|
1390
|
+
members: [],
|
|
1391
|
+
proposals: [],
|
|
1392
|
+
values: [],
|
|
1393
|
+
};
|
|
1342
1394
|
|
|
1343
1395
|
if (snapshot !== undefined) {
|
|
1344
1396
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
1345
|
-
[members, proposals, values] = await Promise.all([
|
|
1397
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
|
|
1346
1398
|
readAndParse<[string, ISequencedClient][]>(storage, baseTree.blobs.quorumMembers),
|
|
1347
1399
|
readAndParse<[number, ISequencedProposal, string[]][]>(storage, baseTree.blobs.quorumProposals),
|
|
1348
1400
|
readAndParse<[string, ICommittedProposal][]>(storage, baseTree.blobs.quorumValues),
|
|
1349
1401
|
]);
|
|
1350
1402
|
}
|
|
1351
1403
|
|
|
1352
|
-
const protocolHandler = await this.initializeProtocolState(
|
|
1353
|
-
attributes,
|
|
1354
|
-
members,
|
|
1355
|
-
proposals,
|
|
1356
|
-
values);
|
|
1357
|
-
|
|
1404
|
+
const protocolHandler = await this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1358
1405
|
return protocolHandler;
|
|
1359
1406
|
}
|
|
1360
1407
|
|
|
1361
1408
|
private async initializeProtocolState(
|
|
1362
1409
|
attributes: IDocumentAttributes,
|
|
1363
|
-
|
|
1364
|
-
proposals: [number, ISequencedProposal, string[]][],
|
|
1365
|
-
values: [string, any][],
|
|
1410
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
1366
1411
|
): Promise<IProtocolHandler> {
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
attributes
|
|
1371
|
-
|
|
1372
|
-
proposals,
|
|
1373
|
-
values,
|
|
1412
|
+
const protocolHandlerBuilder =
|
|
1413
|
+
this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1414
|
+
const protocol = protocolHandlerBuilder(
|
|
1415
|
+
attributes,
|
|
1416
|
+
quorumSnapshot,
|
|
1374
1417
|
(key, value) => this.submitMessage(MessageType.Propose, { key, value }),
|
|
1418
|
+
this._initialClients ?? [],
|
|
1375
1419
|
);
|
|
1376
1420
|
|
|
1421
|
+
this._initialClients = undefined;
|
|
1422
|
+
|
|
1377
1423
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
1378
1424
|
|
|
1379
1425
|
protocol.quorum.on("error", (error) => {
|
|
@@ -1494,17 +1540,30 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1494
1540
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1495
1541
|
deltaManager.inboundSignal.pause();
|
|
1496
1542
|
|
|
1497
|
-
deltaManager.on("connect", (details: IConnectionDetails,
|
|
1498
|
-
|
|
1499
|
-
|
|
1543
|
+
deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
|
|
1544
|
+
if (this._protocolHandler === undefined) {
|
|
1545
|
+
// Store the initial clients so that they can be submitted to the
|
|
1546
|
+
// protocol handler when it is created.
|
|
1547
|
+
this._initialClients = details.initialClients;
|
|
1548
|
+
} else {
|
|
1549
|
+
// When reconnecting, the protocol handler is already created,
|
|
1550
|
+
// so we can update the audience right now.
|
|
1551
|
+
this._protocolHandler.audience.clear();
|
|
1500
1552
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1553
|
+
for (const priorClient of details.initialClients ?? []) {
|
|
1554
|
+
this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
|
|
1555
|
+
}
|
|
1503
1556
|
}
|
|
1504
1557
|
|
|
1558
|
+
const deltaManagerForCatchingUp =
|
|
1559
|
+
this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
|
|
1560
|
+
this.deltaManager
|
|
1561
|
+
: undefined;
|
|
1562
|
+
|
|
1505
1563
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1506
1564
|
this.connectionMode,
|
|
1507
1565
|
details,
|
|
1566
|
+
deltaManagerForCatchingUp,
|
|
1508
1567
|
);
|
|
1509
1568
|
});
|
|
1510
1569
|
|
|
@@ -1730,14 +1789,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1730
1789
|
private processSignal(message: ISignalMessage) {
|
|
1731
1790
|
// No clientId indicates a system signal message.
|
|
1732
1791
|
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
|
-
}
|
|
1792
|
+
this.protocolHandler.processSignal(message);
|
|
1741
1793
|
} else {
|
|
1742
1794
|
const local = this.clientId === message.clientId;
|
|
1743
1795
|
this.context.processSignal(message, local);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions";
|
|
8
8
|
import {
|
|
9
|
+
FetchSource,
|
|
9
10
|
IDocumentStorageService,
|
|
10
11
|
IDocumentStorageServicePolicies,
|
|
11
12
|
ISummaryContext,
|
|
@@ -65,8 +66,13 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
65
66
|
return this.storageGetter().readBlob(id);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
public async getVersions(
|
|
69
|
-
|
|
69
|
+
public async getVersions(
|
|
70
|
+
versionId: string | null,
|
|
71
|
+
count: number,
|
|
72
|
+
scenarioName?: string,
|
|
73
|
+
fetchSource?: FetchSource,
|
|
74
|
+
): Promise<IVersion[]> {
|
|
75
|
+
return this.storageGetter().getVersions(versionId, count, scenarioName, fetchSource);
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
|
package/src/deltaManager.ts
CHANGED
|
@@ -410,7 +410,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
410
410
|
|
|
411
411
|
if (prefetchType !== "none") {
|
|
412
412
|
const cacheOnly = prefetchType === "cached";
|
|
413
|
-
await this.fetchMissingDeltasCore(
|
|
413
|
+
await this.fetchMissingDeltasCore(`DocumentOpen_${prefetchType}`, cacheOnly);
|
|
414
414
|
|
|
415
415
|
// Keep going with fetching ops from storage once we have all cached ops in.
|
|
416
416
|
// But do not block load and make this request async / not blocking this api.
|
|
@@ -418,7 +418,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
418
418
|
// (which in most cases will happen when we are done processing cached ops)
|
|
419
419
|
if (cacheOnly) {
|
|
420
420
|
// fire and forget
|
|
421
|
-
this.fetchMissingDeltas("
|
|
421
|
+
this.fetchMissingDeltas("PostDocumentOpen");
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
|
|
@@ -453,6 +453,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
453
453
|
private async getDeltas(
|
|
454
454
|
from: number, // inclusive
|
|
455
455
|
to: number | undefined, // exclusive
|
|
456
|
+
fetchReason: string,
|
|
456
457
|
callback: (messages: ISequencedDocumentMessage[]) => void,
|
|
457
458
|
cacheOnly: boolean) {
|
|
458
459
|
const docService = this.serviceProvider();
|
|
@@ -473,7 +474,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
473
474
|
// received through delta stream. Validate that before moving forward.
|
|
474
475
|
if (this.lastQueuedSequenceNumber >= lastExpectedOp) {
|
|
475
476
|
this.logger.sendPerformanceEvent({
|
|
476
|
-
reason:
|
|
477
|
+
reason: fetchReason,
|
|
477
478
|
eventName: "ExtraStorageCall",
|
|
478
479
|
early: true,
|
|
479
480
|
from,
|
|
@@ -521,7 +522,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
521
522
|
to, // exclusive
|
|
522
523
|
controller.signal,
|
|
523
524
|
cacheOnly,
|
|
524
|
-
|
|
525
|
+
fetchReason);
|
|
525
526
|
|
|
526
527
|
// eslint-disable-next-line no-constant-condition
|
|
527
528
|
while (true) {
|
|
@@ -876,6 +877,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
876
877
|
await this.getDeltas(
|
|
877
878
|
from,
|
|
878
879
|
to,
|
|
880
|
+
fetchReason,
|
|
879
881
|
(messages) => {
|
|
880
882
|
this.refreshDelayInfo(this.deltaStorageDelayId);
|
|
881
883
|
this.enqueueMessages(messages, fetchReason);
|
package/src/deltaQueue.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions";
|
|
7
|
-
import { assert, performance,
|
|
7
|
+
import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
8
8
|
import Deque from "double-ended-queue";
|
|
9
9
|
|
|
10
10
|
export interface IDeltaQueueWriter<T> {
|
|
@@ -30,7 +30,7 @@ export class DeltaQueue<T>
|
|
|
30
30
|
* When processing is ongoing, holds a deferred that will resolve once processing stops.
|
|
31
31
|
* Undefined when not processing.
|
|
32
32
|
*/
|
|
33
|
-
private
|
|
33
|
+
private processingPromise: Promise<{ count: number; duration: number; }> | undefined;
|
|
34
34
|
|
|
35
35
|
public get disposed(): boolean {
|
|
36
36
|
return this.isDisposed;
|
|
@@ -48,13 +48,11 @@ export class DeltaQueue<T>
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
public get idle(): boolean {
|
|
51
|
-
return this.
|
|
51
|
+
return this.processingPromise === undefined && this.q.length === 0;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
public async waitTillProcessingDone()
|
|
55
|
-
|
|
56
|
-
return this.processingDeferred.promise;
|
|
57
|
-
}
|
|
54
|
+
public async waitTillProcessingDone() {
|
|
55
|
+
return this.processingPromise ?? { count: 0, duration: 0 };
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/**
|
|
@@ -98,7 +96,7 @@ export class DeltaQueue<T>
|
|
|
98
96
|
this.pauseCount++;
|
|
99
97
|
// If called from within the processing loop, we are in the middle of processing an op. Return a promise
|
|
100
98
|
// that will resolve when processing has actually stopped.
|
|
101
|
-
|
|
99
|
+
await this.waitTillProcessingDone();
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
public resume(): void {
|
|
@@ -113,20 +111,31 @@ export class DeltaQueue<T>
|
|
|
113
111
|
* not already started.
|
|
114
112
|
*/
|
|
115
113
|
private ensureProcessing() {
|
|
116
|
-
if (
|
|
117
|
-
this.processingDeferred = new Deferred<void>();
|
|
114
|
+
if (this.anythingToProcess() && this.processingPromise === undefined) {
|
|
118
115
|
// Use a resolved promise to start the processing on a separate stack.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.processDeltas();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
this.processingPromise = Promise.resolve().then(() => {
|
|
117
|
+
assert(this.processingPromise !== undefined, "reentrancy?");
|
|
118
|
+
const result = this.processDeltas();
|
|
119
|
+
assert(this.processingPromise !== undefined, "reentrancy?");
|
|
120
|
+
// WARNING: Do not move next line to .finally() clause!
|
|
121
|
+
// It runs async and creates a race condition where incoming ensureProcessing() call observes
|
|
122
|
+
// from previous run while previous run is over (but finally clause was not scheduled yet)
|
|
123
|
+
this.processingPromise = undefined;
|
|
124
|
+
return result;
|
|
125
|
+
}).catch((error) => {
|
|
126
|
+
this.error = error;
|
|
127
|
+
this.processingPromise = undefined;
|
|
128
|
+
this.emit("error", error);
|
|
129
|
+
return { count: 0, duration: 0 };
|
|
126
130
|
});
|
|
131
|
+
assert(this.processingPromise !== undefined, "processDeltas() should run async");
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
private anythingToProcess() {
|
|
136
|
+
return this.q.length !== 0 && !this.paused && this.error === undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
/**
|
|
131
140
|
* Executes the delta processing loop until a stop condition is reached.
|
|
132
141
|
*/
|
|
@@ -136,24 +145,21 @@ export class DeltaQueue<T>
|
|
|
136
145
|
|
|
137
146
|
// For grouping to work we must process all local messages immediately and in the single turn.
|
|
138
147
|
// So loop over them until no messages to process, we have become paused, or hit an error.
|
|
139
|
-
while (
|
|
148
|
+
while (this.anythingToProcess()) {
|
|
140
149
|
// Get the next message in the queue
|
|
141
150
|
const next = this.q.shift();
|
|
142
151
|
count++;
|
|
143
152
|
// Process the message.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.emit("op", next);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
this.error = error;
|
|
151
|
-
this.emit("error", error);
|
|
152
|
-
}
|
|
153
|
+
// We know next is defined since we did a length check just prior to shifting.
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
155
|
+
this.worker(next!);
|
|
156
|
+
this.emit("op", next);
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
const duration = performance.now() - start;
|
|
155
160
|
if (this.q.length === 0) {
|
|
156
|
-
this.emit("idle", count,
|
|
161
|
+
this.emit("idle", count, duration);
|
|
157
162
|
}
|
|
163
|
+
return { count, duration };
|
|
158
164
|
}
|
|
159
165
|
}
|
package/src/index.ts
CHANGED
package/src/loader.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
import { Container, IPendingContainerState } from "./container";
|
|
49
49
|
import { IParsedUrl, parseUrl } from "./utils";
|
|
50
50
|
import { pkgVersion } from "./packageVersion";
|
|
51
|
+
import { ProtocolHandlerBuilder } from "./protocol";
|
|
51
52
|
|
|
52
53
|
function canUseCache(request: IRequest): boolean {
|
|
53
54
|
if (request.headers === undefined) {
|
|
@@ -211,7 +212,13 @@ export interface ILoaderProps {
|
|
|
211
212
|
/**
|
|
212
213
|
* The configuration provider which may be used to control features.
|
|
213
214
|
*/
|
|
214
|
-
|
|
215
|
+
readonly configProvider?: IConfigProviderBase;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Optional property for allowing the container to use a custom
|
|
219
|
+
* protocol implementation for handling the quorum and/or the audience.
|
|
220
|
+
*/
|
|
221
|
+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
/**
|
|
@@ -278,6 +285,7 @@ export class Loader implements IHostLoader {
|
|
|
278
285
|
private readonly containers = new Map<string, Promise<Container>>();
|
|
279
286
|
public readonly services: ILoaderServices;
|
|
280
287
|
private readonly mc: MonitoringContext;
|
|
288
|
+
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
|
|
281
289
|
|
|
282
290
|
constructor(loaderProps: ILoaderProps) {
|
|
283
291
|
const scope: FluidObject<ILoader> = { ...loaderProps.scope };
|
|
@@ -306,6 +314,7 @@ export class Loader implements IHostLoader {
|
|
|
306
314
|
};
|
|
307
315
|
this.mc = loggerToMonitoringContext(
|
|
308
316
|
ChildLogger.create(this.services.subLogger, "Loader"));
|
|
317
|
+
this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
public get IFluidRouter(): IFluidRouter { return this; }
|
|
@@ -314,6 +323,7 @@ export class Loader implements IHostLoader {
|
|
|
314
323
|
const container = await Container.createDetached(
|
|
315
324
|
this,
|
|
316
325
|
codeDetails,
|
|
326
|
+
this.protocolHandlerBuilder,
|
|
317
327
|
);
|
|
318
328
|
|
|
319
329
|
if (this.cachingEnabled) {
|
|
@@ -330,7 +340,7 @@ export class Loader implements IHostLoader {
|
|
|
330
340
|
}
|
|
331
341
|
|
|
332
342
|
public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
|
|
333
|
-
return Container.rehydrateDetachedFromSnapshot(this, snapshot);
|
|
343
|
+
return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
|
|
334
344
|
}
|
|
335
345
|
|
|
336
346
|
public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
|
|
@@ -482,6 +492,7 @@ export class Loader implements IHostLoader {
|
|
|
482
492
|
loadMode: request.headers?.[LoaderHeader.loadMode],
|
|
483
493
|
},
|
|
484
494
|
pendingLocalState,
|
|
495
|
+
this.protocolHandlerBuilder,
|
|
485
496
|
);
|
|
486
497
|
}
|
|
487
498
|
}
|
package/src/packageVersion.ts
CHANGED