@fluidframework/container-loader 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258
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/CHANGELOG.md +162 -0
- package/README.md +10 -6
- package/dist/audience.d.ts +1 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +5 -3
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +6 -6
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +97 -93
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +19 -15
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +59 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +48 -38
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +447 -325
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +22 -70
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +24 -221
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +47 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +21 -10
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/debugLogger.js +95 -0
- package/dist/debugLogger.js.map +1 -0
- package/dist/deltaManager.d.ts +21 -9
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +114 -66
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +10 -10
- package/dist/deltaQueue.js.map +1 -1
- package/dist/disposal.d.ts +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/disposal.js +25 -0
- package/dist/disposal.js.map +1 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/loader.d.ts +23 -5
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +82 -51
- package/dist/loader.js.map +1 -1
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/noopHeuristic.js +90 -0
- package/dist/noopHeuristic.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +9 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +26 -7
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts +1 -14
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -29
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +4 -4
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +30 -11
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +1 -0
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +4 -2
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +6 -6
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +74 -67
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +19 -15
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +36 -36
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +48 -38
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +414 -292
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +22 -70
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +24 -221
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +43 -12
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +21 -10
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +30 -0
- package/lib/debugLogger.d.ts.map +1 -0
- package/lib/debugLogger.js +91 -0
- package/lib/debugLogger.js.map +1 -0
- package/lib/deltaManager.d.ts +21 -9
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +88 -37
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +3 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/disposal.d.ts +13 -0
- package/lib/disposal.d.ts.map +1 -0
- package/lib/disposal.js +21 -0
- package/lib/disposal.js.map +1 -0
- package/lib/error.d.ts +23 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +28 -0
- package/lib/error.js.map +1 -0
- package/lib/loader.d.ts +23 -5
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +82 -51
- package/lib/loader.js.map +1 -1
- package/lib/noopHeuristic.d.ts +23 -0
- package/lib/noopHeuristic.d.ts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.js} +31 -42
- package/lib/noopHeuristic.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +9 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +24 -6
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts +1 -14
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -26
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +25 -7
- package/lib/utils.js.map +1 -1
- package/package.json +26 -28
- package/src/audience.ts +7 -1
- package/src/catchUpMonitor.ts +2 -2
- package/src/connectionManager.ts +76 -52
- package/src/connectionStateHandler.ts +46 -48
- package/src/container.ts +561 -326
- package/src/containerContext.ts +31 -349
- package/src/containerStorageAdapter.ts +49 -6
- package/src/contracts.ts +27 -13
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +93 -36
- package/src/deltaQueue.ts +2 -1
- package/src/disposal.ts +25 -0
- package/src/error.ts +44 -0
- package/src/loader.ts +84 -36
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +38 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +26 -16
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/quorum.ts +1 -40
- package/src/retriableDocumentStorageService.ts +3 -4
- package/src/utils.ts +33 -8
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js +0 -101
- package/dist/collabWindowTracker.js.map +0 -1
- package/dist/deltaManagerProxy.d.ts +0 -42
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -79
- package/dist/deltaManagerProxy.js.map +0 -1
- package/lib/collabWindowTracker.d.ts +0 -19
- package/lib/collabWindowTracker.d.ts.map +0 -1
- package/lib/collabWindowTracker.js.map +0 -1
- package/lib/deltaManagerProxy.d.ts +0 -42
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -74
- package/lib/deltaManagerProxy.js.map +0 -1
- package/src/deltaManagerProxy.ts +0 -109
package/src/loader.ts
CHANGED
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { v4 as uuid } from "uuid";
|
|
7
|
-
import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
|
|
8
7
|
import {
|
|
9
8
|
ITelemetryLoggerExt,
|
|
10
|
-
ChildLogger,
|
|
11
|
-
DebugLogger,
|
|
12
9
|
IConfigProviderBase,
|
|
13
|
-
loggerToMonitoringContext,
|
|
14
10
|
mixinMonitoringContext,
|
|
15
11
|
MonitoringContext,
|
|
16
12
|
PerformanceEvent,
|
|
17
13
|
sessionStorageConfigProvider,
|
|
14
|
+
createChildMonitoringContext,
|
|
15
|
+
UsageError,
|
|
18
16
|
} from "@fluidframework/telemetry-utils";
|
|
19
17
|
import {
|
|
18
|
+
ITelemetryBaseLogger,
|
|
20
19
|
FluidObject,
|
|
21
20
|
IFluidRouter,
|
|
22
21
|
IRequest,
|
|
@@ -39,18 +38,15 @@ import {
|
|
|
39
38
|
IResolvedUrl,
|
|
40
39
|
IUrlResolver,
|
|
41
40
|
} from "@fluidframework/driver-definitions";
|
|
42
|
-
import {
|
|
41
|
+
import { IClientDetails } from "@fluidframework/protocol-definitions";
|
|
43
42
|
import { Container, IPendingContainerState } from "./container";
|
|
44
43
|
import { IParsedUrl, parseUrl } from "./utils";
|
|
45
44
|
import { pkgVersion } from "./packageVersion";
|
|
46
45
|
import { ProtocolHandlerBuilder } from "./protocol";
|
|
46
|
+
import { DebugLogger } from "./debugLogger";
|
|
47
47
|
|
|
48
48
|
function canUseCache(request: IRequest): boolean {
|
|
49
|
-
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return request.headers[LoaderHeader.cache] !== false;
|
|
49
|
+
return request.headers?.[LoaderHeader.cache] === true;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
function ensureResolvedUrlDefined(
|
|
@@ -69,6 +65,9 @@ export class RelativeLoader implements ILoader {
|
|
|
69
65
|
private readonly loader: ILoader | undefined,
|
|
70
66
|
) {}
|
|
71
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
70
|
+
*/
|
|
72
71
|
public get IFluidRouter(): IFluidRouter {
|
|
73
72
|
return this;
|
|
74
73
|
}
|
|
@@ -100,6 +99,9 @@ export class RelativeLoader implements ILoader {
|
|
|
100
99
|
return this.loader.resolve(request);
|
|
101
100
|
}
|
|
102
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
104
|
+
*/
|
|
103
105
|
public async request(request: IRequest): Promise<IResponse> {
|
|
104
106
|
if (request.url.startsWith("/")) {
|
|
105
107
|
const container = await this.resolve(request);
|
|
@@ -149,7 +151,7 @@ export interface ICodeDetailsLoader extends Partial<IProvideFluidCodeDetailsComp
|
|
|
149
151
|
* Load the code module (package) that is capable to interact with the document.
|
|
150
152
|
*
|
|
151
153
|
* @param source - Code proposal that articulates the current schema the document is written in.
|
|
152
|
-
* @returns
|
|
154
|
+
* @returns Code module entry point along with the code details associated with it.
|
|
153
155
|
*/
|
|
154
156
|
load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
|
|
155
157
|
}
|
|
@@ -343,15 +345,33 @@ export class Loader implements IHostLoader {
|
|
|
343
345
|
protocolHandlerBuilder,
|
|
344
346
|
subLogger: subMc.logger,
|
|
345
347
|
};
|
|
346
|
-
this.mc =
|
|
348
|
+
this.mc = createChildMonitoringContext({
|
|
349
|
+
logger: this.services.subLogger,
|
|
350
|
+
namespace: "Loader",
|
|
351
|
+
});
|
|
347
352
|
}
|
|
348
353
|
|
|
354
|
+
/**
|
|
355
|
+
* @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
356
|
+
*/
|
|
349
357
|
public get IFluidRouter(): IFluidRouter {
|
|
350
358
|
return this;
|
|
351
359
|
}
|
|
352
360
|
|
|
353
|
-
public async createDetachedContainer(
|
|
354
|
-
|
|
361
|
+
public async createDetachedContainer(
|
|
362
|
+
codeDetails: IFluidCodeDetails,
|
|
363
|
+
createDetachedProps?: {
|
|
364
|
+
canReconnect?: boolean;
|
|
365
|
+
clientDetailsOverride?: IClientDetails;
|
|
366
|
+
},
|
|
367
|
+
): Promise<IContainer> {
|
|
368
|
+
const container = await Container.createDetached(
|
|
369
|
+
{
|
|
370
|
+
...createDetachedProps,
|
|
371
|
+
...this.services,
|
|
372
|
+
},
|
|
373
|
+
codeDetails,
|
|
374
|
+
);
|
|
355
375
|
|
|
356
376
|
if (this.cachingEnabled) {
|
|
357
377
|
container.once("attached", () => {
|
|
@@ -366,8 +386,20 @@ export class Loader implements IHostLoader {
|
|
|
366
386
|
return container;
|
|
367
387
|
}
|
|
368
388
|
|
|
369
|
-
public async rehydrateDetachedContainerFromSnapshot(
|
|
370
|
-
|
|
389
|
+
public async rehydrateDetachedContainerFromSnapshot(
|
|
390
|
+
snapshot: string,
|
|
391
|
+
createDetachedProps?: {
|
|
392
|
+
canReconnect?: boolean;
|
|
393
|
+
clientDetailsOverride?: IClientDetails;
|
|
394
|
+
},
|
|
395
|
+
): Promise<IContainer> {
|
|
396
|
+
return Container.rehydrateDetachedFromSnapshot(
|
|
397
|
+
{
|
|
398
|
+
...createDetachedProps,
|
|
399
|
+
...this.services,
|
|
400
|
+
},
|
|
401
|
+
snapshot,
|
|
402
|
+
);
|
|
371
403
|
}
|
|
372
404
|
|
|
373
405
|
public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
|
|
@@ -381,6 +413,9 @@ export class Loader implements IHostLoader {
|
|
|
381
413
|
});
|
|
382
414
|
}
|
|
383
415
|
|
|
416
|
+
/**
|
|
417
|
+
* @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
418
|
+
*/
|
|
384
419
|
public async request(request: IRequest): Promise<IResponse> {
|
|
385
420
|
return PerformanceEvent.timedExecAsync(
|
|
386
421
|
this.mc.logger,
|
|
@@ -407,16 +442,23 @@ export class Loader implements IHostLoader {
|
|
|
407
442
|
this.containers.set(key, containerP);
|
|
408
443
|
containerP
|
|
409
444
|
.then((container) => {
|
|
410
|
-
// If the container is closed or becomes closed after we resolve it,
|
|
411
|
-
|
|
445
|
+
// If the container is closed/disposed or becomes closed/disposed after we resolve it,
|
|
446
|
+
// remove it from the cache.
|
|
447
|
+
if (container.closed || container.disposed) {
|
|
412
448
|
this.containers.delete(key);
|
|
413
449
|
} else {
|
|
414
450
|
container.once("closed", () => {
|
|
415
451
|
this.containers.delete(key);
|
|
416
452
|
});
|
|
453
|
+
container.once("disposed", () => {
|
|
454
|
+
this.containers.delete(key);
|
|
455
|
+
});
|
|
417
456
|
}
|
|
418
457
|
})
|
|
419
|
-
.catch((error) => {
|
|
458
|
+
.catch((error) => {
|
|
459
|
+
// If an error occured while resolving the container request, then remove it from the cache.
|
|
460
|
+
this.containers.delete(key);
|
|
461
|
+
});
|
|
420
462
|
}
|
|
421
463
|
|
|
422
464
|
private async resolveCore(
|
|
@@ -447,11 +489,29 @@ export class Loader implements IHostLoader {
|
|
|
447
489
|
// If set in both query string and headers, use query string. Also write the value from the query string into the header either way.
|
|
448
490
|
request.headers[LoaderHeader.version] =
|
|
449
491
|
parsed.version ?? request.headers[LoaderHeader.version];
|
|
492
|
+
const cacheHeader = request.headers[LoaderHeader.cache];
|
|
450
493
|
const canCache =
|
|
451
|
-
|
|
452
|
-
|
|
494
|
+
// Take header value if present, else use ILoaderOptions.cache value
|
|
495
|
+
(cacheHeader !== undefined ? cacheHeader === true : this.cachingEnabled) &&
|
|
453
496
|
pendingLocalState === undefined;
|
|
454
|
-
const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber]
|
|
497
|
+
const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] as
|
|
498
|
+
| number
|
|
499
|
+
| undefined;
|
|
500
|
+
const opsBeforeReturn = request.headers[LoaderHeader.loadMode]?.opsBeforeReturn as
|
|
501
|
+
| string
|
|
502
|
+
| undefined;
|
|
503
|
+
|
|
504
|
+
if (
|
|
505
|
+
opsBeforeReturn === "sequenceNumber" &&
|
|
506
|
+
(fromSequenceNumber === undefined || fromSequenceNumber < 0)
|
|
507
|
+
) {
|
|
508
|
+
// If opsBeforeReturn is set to "sequenceNumber", then fromSequenceNumber should be set to a non-negative integer.
|
|
509
|
+
throw new UsageError("sequenceNumber must be set to a non-negative integer");
|
|
510
|
+
} else if (opsBeforeReturn !== "sequenceNumber" && fromSequenceNumber !== undefined) {
|
|
511
|
+
// If opsBeforeReturn is not set to "sequenceNumber", then fromSequenceNumber should be undefined (default value).
|
|
512
|
+
// In this case, we should throw an error since opsBeforeReturn is not explicitly set to "sequenceNumber".
|
|
513
|
+
throw new UsageError('opsBeforeReturn must be set to "sequenceNumber"');
|
|
514
|
+
}
|
|
455
515
|
|
|
456
516
|
let container: Container;
|
|
457
517
|
if (canCache) {
|
|
@@ -468,24 +528,11 @@ export class Loader implements IHostLoader {
|
|
|
468
528
|
container = await this.loadContainer(request, resolvedAsFluid, pendingLocalState);
|
|
469
529
|
}
|
|
470
530
|
|
|
471
|
-
if (container.deltaManager.lastSequenceNumber <= fromSequenceNumber) {
|
|
472
|
-
await new Promise<void>((resolve, reject) => {
|
|
473
|
-
function opHandler(message: ISequencedDocumentMessage) {
|
|
474
|
-
if (message.sequenceNumber > fromSequenceNumber) {
|
|
475
|
-
resolve();
|
|
476
|
-
container.removeListener("op", opHandler);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
container.on("op", opHandler);
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
531
|
return { container, parsed };
|
|
485
532
|
}
|
|
486
533
|
|
|
487
534
|
private get cachingEnabled() {
|
|
488
|
-
return this.services.options.cache
|
|
535
|
+
return this.services.options.cache === true;
|
|
489
536
|
}
|
|
490
537
|
|
|
491
538
|
private async loadContainer(
|
|
@@ -499,6 +546,7 @@ export class Loader implements IHostLoader {
|
|
|
499
546
|
version: request.headers?.[LoaderHeader.version] ?? undefined,
|
|
500
547
|
loadMode: request.headers?.[LoaderHeader.loadMode],
|
|
501
548
|
pendingLocalState,
|
|
549
|
+
loadToSequenceNumber: request.headers?.[LoaderHeader.sequenceNumber],
|
|
502
550
|
},
|
|
503
551
|
{
|
|
504
552
|
canReconnect: request.headers?.[LoaderHeader.reconnect],
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
7
|
+
import { assert, Timer } from "@fluidframework/core-utils";
|
|
8
|
+
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
9
|
+
import { isRuntimeMessage } from "@fluidframework/driver-utils";
|
|
10
|
+
import { IEvent } from "@fluidframework/core-interfaces";
|
|
9
11
|
|
|
10
12
|
const defaultNoopTimeFrequency = 2000;
|
|
11
13
|
const defaultNoopCountFrequency = 50;
|
|
12
14
|
|
|
15
|
+
export interface INoopSenderEvents extends IEvent {
|
|
16
|
+
(event: "wantsNoop", listener: () => void);
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
// Here are key considerations when deciding conditions for when to send non-immediate noops:
|
|
14
20
|
// 1. Sending them too often results in increase in file size and bandwidth, as well as catch up performance
|
|
15
21
|
// 2. Sending too infrequently ensures that collab window is large, and as result Sequence DDS would have
|
|
@@ -29,21 +35,21 @@ const defaultNoopCountFrequency = 50;
|
|
|
29
35
|
// server timeout of 2000ms should be reconsidered to be increased.
|
|
30
36
|
// 2. If there are more than 50 ops received without sending any ops, send noop to keep collab window small.
|
|
31
37
|
// Note that system ops (including noops themselves) are excluded, so it's 1 noop per 50 real ops.
|
|
32
|
-
export class
|
|
33
|
-
private
|
|
38
|
+
export class NoopHeuristic extends TypedEventEmitter<INoopSenderEvents> {
|
|
39
|
+
private opsProcessedSinceOpSent = 0;
|
|
34
40
|
private readonly timer: Timer | undefined;
|
|
35
41
|
|
|
36
42
|
constructor(
|
|
37
|
-
private readonly submit: (type: MessageType) => void,
|
|
38
43
|
NoopTimeFrequency: number = defaultNoopTimeFrequency,
|
|
39
44
|
private readonly NoopCountFrequency: number = defaultNoopCountFrequency,
|
|
40
45
|
) {
|
|
46
|
+
super();
|
|
41
47
|
if (NoopTimeFrequency !== Infinity) {
|
|
42
48
|
this.timer = new Timer(NoopTimeFrequency, () => {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
if (this.
|
|
46
|
-
this.
|
|
49
|
+
// We allow the timer to expire even if an op is sent or we disconnect.
|
|
50
|
+
// This condition is to guard against trying to send a noop anyway in that case.
|
|
51
|
+
if (this.opsProcessedSinceOpSent !== 0) {
|
|
52
|
+
this.emit("wantsNoop");
|
|
47
53
|
}
|
|
48
54
|
});
|
|
49
55
|
}
|
|
@@ -52,17 +58,7 @@ export class CollabWindowTracker {
|
|
|
52
58
|
/**
|
|
53
59
|
* Schedules as ack to the server to update the reference sequence number
|
|
54
60
|
*/
|
|
55
|
-
public
|
|
56
|
-
message: ISequencedDocumentMessage,
|
|
57
|
-
immediateNoOp: boolean,
|
|
58
|
-
): void {
|
|
59
|
-
// While processing a message, an immediate no-op can be requested.
|
|
60
|
-
// i.e. to expedite approve or commit phase of quorum.
|
|
61
|
-
if (immediateNoOp) {
|
|
62
|
-
this.submitNoop(true /* immediate */);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
61
|
+
public notifyMessageProcessed(message: ISequencedDocumentMessage): void {
|
|
66
62
|
// We don't acknowledge no-ops to avoid acknowledgement cycles (i.e. ack the MSN
|
|
67
63
|
// update, which updates the MSN, then ack the update, etc...).
|
|
68
64
|
// Intent here is for runtime (and DDSes) not to keep too much tracking state / memory
|
|
@@ -71,22 +67,27 @@ export class CollabWindowTracker {
|
|
|
71
67
|
return;
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
this.
|
|
75
|
-
if (this.
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
this.opsProcessedSinceOpSent++;
|
|
71
|
+
if (this.opsProcessedSinceOpSent === this.NoopCountFrequency) {
|
|
72
|
+
// Wait to send a noop if we are still synchronously processing ops. This guards against two things:
|
|
73
|
+
// 1. If we're processing many ops, we may pass the frequency threshold many times. We only need to send one noop at the very end in this case.
|
|
74
|
+
// 2. We may send another (non-noop) op in response to processing those ops, e.g. an Accept op.
|
|
75
|
+
queueMicrotask(() => {
|
|
76
|
+
if (this.opsProcessedSinceOpSent >= this.NoopCountFrequency) {
|
|
77
|
+
this.emit("wantsNoop");
|
|
78
|
+
assert(
|
|
79
|
+
this.opsProcessedSinceOpSent === 0,
|
|
80
|
+
0x243 /* "Expected a noop to be synchronously sent" */,
|
|
81
|
+
);
|
|
83
82
|
}
|
|
84
83
|
return;
|
|
85
84
|
});
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
if (this.timer !== undefined) {
|
|
89
|
-
if
|
|
88
|
+
// Start the timer if we newly have ops that want a noop.
|
|
89
|
+
// If the timer was already running (e.g. we surpassed the op count and sent a noop) this will reset it to its full duration.
|
|
90
|
+
if (this.opsProcessedSinceOpSent === 1) {
|
|
90
91
|
this.timer.restart();
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -94,23 +95,13 @@ export class CollabWindowTracker {
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
this.submit(immediate ? (MessageType2.Accept as unknown as MessageType) : MessageType.NoOp);
|
|
101
|
-
assert(
|
|
102
|
-
this.opsCountSinceNoop === 0,
|
|
103
|
-
0x243 /* "stopSequenceNumberUpdate should be called as result of sending any op!" */,
|
|
104
|
-
);
|
|
98
|
+
public notifyDisconnect(): void {
|
|
99
|
+
// No need to noop for any ops processed prior to disconnect - we are already removed from MSN calculation.
|
|
100
|
+
this.opsProcessedSinceOpSent = 0;
|
|
105
101
|
}
|
|
106
102
|
|
|
107
|
-
public
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// keeps sending ops. In most cases it's actually better to let it expire (at most - 4 times per second)
|
|
111
|
-
// for nothing, then have a ton of set/reset cycles.
|
|
112
|
-
// Note that Timer.restart() is smart and will not change timer expiration if we keep extending timer
|
|
113
|
-
// expiration - it will restart the timer instead when it fires with adjusted expiration.
|
|
114
|
-
// this.timer.clear();
|
|
103
|
+
public notifyMessageSent(): void {
|
|
104
|
+
// Sending any message is as good as a noop for updating MSN.
|
|
105
|
+
this.opsProcessedSinceOpSent = 0;
|
|
115
106
|
}
|
|
116
107
|
}
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IAudienceOwner } from "@fluidframework/container-definitions";
|
|
7
|
+
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
7
8
|
import {
|
|
8
9
|
IProtocolHandler as IBaseProtocolHandler,
|
|
9
10
|
IQuorumSnapshot,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
ISignalMessage,
|
|
19
20
|
MessageType,
|
|
20
21
|
} from "@fluidframework/protocol-definitions";
|
|
21
|
-
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
22
22
|
|
|
23
23
|
// "term" was an experimental feature that is being removed. The only safe value to use is 1.
|
|
24
24
|
export const OnlyValidTermValue = 1 as const;
|
|
@@ -30,17 +30,6 @@ export enum SignalType {
|
|
|
30
30
|
Clear = "clear", // used only by client for synthetic signals
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
* ADO: #4277: ConnectionStateHandler can mutate Quorum members, but shouldn't
|
|
35
|
-
* This interface might go away after the above ADO item is done
|
|
36
|
-
*/
|
|
37
|
-
export interface ILocalSequencedClient extends ISequencedClient {
|
|
38
|
-
/**
|
|
39
|
-
* True if the client should have left the quorum, false otherwise
|
|
40
|
-
*/
|
|
41
|
-
shouldHaveLeft?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
33
|
/**
|
|
45
34
|
* Function to be used for creating a protocol handler.
|
|
46
35
|
*/
|
|
@@ -60,12 +49,12 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
60
49
|
attributes: IDocumentAttributes,
|
|
61
50
|
quorumSnapshot: IQuorumSnapshot,
|
|
62
51
|
sendProposal: (key: string, value: any) => number,
|
|
63
|
-
readonly audience: IAudienceOwner,
|
|
52
|
+
public readonly audience: IAudienceOwner,
|
|
53
|
+
private readonly shouldClientHaveLeft: (clientId: string) => boolean,
|
|
64
54
|
) {
|
|
65
55
|
super(
|
|
66
56
|
attributes.minimumSequenceNumber,
|
|
67
57
|
attributes.sequenceNumber,
|
|
68
|
-
OnlyValidTermValue,
|
|
69
58
|
quorumSnapshot.members,
|
|
70
59
|
quorumSnapshot.proposals,
|
|
71
60
|
quorumSnapshot.values,
|
|
@@ -86,7 +75,7 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
86
75
|
message: ISequencedDocumentMessage,
|
|
87
76
|
local: boolean,
|
|
88
77
|
): IProcessMessageResult {
|
|
89
|
-
const client:
|
|
78
|
+
const client: ISequencedClient | undefined = this.quorum.getMember(message.clientId);
|
|
90
79
|
|
|
91
80
|
// Check and report if we're getting messages from a clientId that we previously
|
|
92
81
|
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
@@ -96,7 +85,10 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
96
85
|
throw new Error("Remote message's clientId is missing from the quorum");
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
|
|
88
|
+
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
89
|
+
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
90
|
+
// document we don't need to blow up aggressively.
|
|
91
|
+
if (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {
|
|
100
92
|
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
101
93
|
throw new Error("Remote message's clientId already should have left");
|
|
102
94
|
}
|
|
@@ -138,3 +130,21 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
138
130
|
}
|
|
139
131
|
}
|
|
140
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Function to check whether the protocol handler should process the Signal.
|
|
136
|
+
* The protocol handler should strictly handle only ClientJoin, ClientLeave
|
|
137
|
+
* and Clear signal types.
|
|
138
|
+
*/
|
|
139
|
+
export function protocolHandlerShouldProcessSignal(message: ISignalMessage) {
|
|
140
|
+
// Signal originates from server
|
|
141
|
+
if (message.clientId === null) {
|
|
142
|
+
const innerContent = message.content as { content: unknown; type: string };
|
|
143
|
+
return (
|
|
144
|
+
innerContent.type === SignalType.Clear ||
|
|
145
|
+
innerContent.type === SignalType.ClientJoin ||
|
|
146
|
+
innerContent.type === SignalType.ClientLeave
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IDisposable } from "@fluidframework/
|
|
6
|
+
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
7
|
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
8
8
|
import { ISummaryTree } from "@fluidframework/protocol-definitions";
|
|
9
9
|
|
package/src/quorum.ts
CHANGED
|
@@ -2,47 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import { assert, EventForwarder, doIfNotDisposed } from "@fluidframework/common-utils";
|
|
6
5
|
import { IFluidCodeDetails } from "@fluidframework/core-interfaces";
|
|
7
|
-
import {
|
|
8
|
-
ICommittedProposal,
|
|
9
|
-
IQuorum,
|
|
10
|
-
IQuorumEvents,
|
|
11
|
-
ISequencedClient,
|
|
12
|
-
} from "@fluidframework/protocol-definitions";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Proxies Quorum events.
|
|
16
|
-
*/
|
|
17
|
-
export class QuorumProxy extends EventForwarder<IQuorumEvents> implements IQuorum {
|
|
18
|
-
public readonly propose: (key: string, value: any) => Promise<void>;
|
|
19
|
-
public readonly has: (key: string) => boolean;
|
|
20
|
-
public readonly get: (key: string) => any;
|
|
21
|
-
public readonly getMembers: () => Map<string, ISequencedClient>;
|
|
22
|
-
public readonly getMember: (clientId: string) => ISequencedClient | undefined;
|
|
23
|
-
|
|
24
|
-
constructor(quorum: IQuorum) {
|
|
25
|
-
super(quorum);
|
|
26
|
-
|
|
27
|
-
// This is heavily used object, increase limit at which Node prints warnings.
|
|
28
|
-
super.setMaxListeners(50);
|
|
29
|
-
|
|
30
|
-
this.propose = doIfNotDisposed(this, quorum.propose.bind(quorum));
|
|
31
|
-
this.has = doIfNotDisposed(this, quorum.has.bind(quorum));
|
|
32
|
-
this.get = doIfNotDisposed(this, quorum.get.bind(quorum));
|
|
33
|
-
this.getMembers = doIfNotDisposed(this, quorum.getMembers.bind(quorum));
|
|
34
|
-
this.getMember = doIfNotDisposed(this, quorum.getMember.bind(quorum));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getCodeDetailsFromQuorumValues(
|
|
39
|
-
quorumValues: [string, ICommittedProposal][],
|
|
40
|
-
): IFluidCodeDetails {
|
|
41
|
-
const qValuesMap = new Map(quorumValues);
|
|
42
|
-
const proposal = qValuesMap.get("code");
|
|
43
|
-
assert(proposal !== undefined, 0x2dc /* "Cannot find code proposal" */);
|
|
44
|
-
return proposal?.value as IFluidCodeDetails;
|
|
45
|
-
}
|
|
6
|
+
import { ICommittedProposal } from "@fluidframework/protocol-definitions";
|
|
46
7
|
|
|
47
8
|
export function initQuorumValuesFromCodeDetails(
|
|
48
9
|
source: IFluidCodeDetails,
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { assert } from "@fluidframework/
|
|
7
|
-
import { GenericError } from "@fluidframework/container-utils";
|
|
6
|
+
import { assert } from "@fluidframework/core-utils";
|
|
8
7
|
import {
|
|
9
8
|
FetchSource,
|
|
10
9
|
IDocumentStorageService,
|
|
@@ -18,8 +17,8 @@ import {
|
|
|
18
17
|
ISummaryTree,
|
|
19
18
|
IVersion,
|
|
20
19
|
} from "@fluidframework/protocol-definitions";
|
|
21
|
-
import { IDisposable } from "@fluidframework/
|
|
22
|
-
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
20
|
+
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
21
|
+
import { GenericError, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
23
22
|
import { runWithRetry } from "@fluidframework/driver-utils";
|
|
24
23
|
|
|
25
24
|
export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
|
package/src/utils.ts
CHANGED
|
@@ -5,19 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import { parse } from "url";
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
stringToBuffer,
|
|
11
|
-
Uint8ArrayToArrayBuffer,
|
|
12
|
-
unreachableCase,
|
|
13
|
-
} from "@fluidframework/common-utils";
|
|
8
|
+
import { stringToBuffer, Uint8ArrayToArrayBuffer } from "@fluid-internal/client-utils";
|
|
9
|
+
import { assert, unreachableCase } from "@fluidframework/core-utils";
|
|
14
10
|
import { ISummaryTree, ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
|
|
15
11
|
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
16
12
|
import {
|
|
13
|
+
CombinedAppAndProtocolSummary,
|
|
17
14
|
DeltaStreamConnectionForbiddenError,
|
|
18
15
|
isCombinedAppAndProtocolSummary,
|
|
19
16
|
} from "@fluidframework/driver-utils";
|
|
20
|
-
import {
|
|
17
|
+
import { DriverErrorTypes } from "@fluidframework/driver-definitions";
|
|
21
18
|
|
|
22
19
|
// This is used when we rehydrate a container from the snapshot. Here we put the blob contents
|
|
23
20
|
// in separate property: blobContents.
|
|
@@ -51,6 +48,34 @@ export function parseUrl(url: string): IParsedUrl | undefined {
|
|
|
51
48
|
: undefined;
|
|
52
49
|
}
|
|
53
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Combine the app summary and protocol summary in 1 tree.
|
|
53
|
+
* @param appSummary - Summary of the app.
|
|
54
|
+
* @param protocolSummary - Summary of the protocol.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export function combineAppAndProtocolSummary(
|
|
58
|
+
appSummary: ISummaryTree,
|
|
59
|
+
protocolSummary: ISummaryTree,
|
|
60
|
+
): CombinedAppAndProtocolSummary {
|
|
61
|
+
assert(
|
|
62
|
+
!isCombinedAppAndProtocolSummary(appSummary),
|
|
63
|
+
0x5a8 /* app summary is already a combined tree! */,
|
|
64
|
+
);
|
|
65
|
+
assert(
|
|
66
|
+
!isCombinedAppAndProtocolSummary(protocolSummary),
|
|
67
|
+
0x5a9 /* protocol summary is already a combined tree! */,
|
|
68
|
+
);
|
|
69
|
+
const createNewSummary: CombinedAppAndProtocolSummary = {
|
|
70
|
+
type: SummaryType.Tree,
|
|
71
|
+
tree: {
|
|
72
|
+
".protocol": protocolSummary,
|
|
73
|
+
".app": appSummary,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
return createNewSummary;
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
/**
|
|
55
80
|
* Converts summary tree (for upload) to snapshot tree (for download).
|
|
56
81
|
* Summary tree blobs contain contents, but snapshot tree blobs normally
|
|
@@ -156,6 +181,6 @@ export function isDeltaStreamConnectionForbiddenError(
|
|
|
156
181
|
return (
|
|
157
182
|
typeof error === "object" &&
|
|
158
183
|
error !== null &&
|
|
159
|
-
error?.errorType ===
|
|
184
|
+
error?.errorType === DriverErrorTypes.deltaStreamConnectionForbidden
|
|
160
185
|
);
|
|
161
186
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
|
|
6
|
-
export declare class CollabWindowTracker {
|
|
7
|
-
private readonly submit;
|
|
8
|
-
private readonly NoopCountFrequency;
|
|
9
|
-
private opsCountSinceNoop;
|
|
10
|
-
private readonly timer;
|
|
11
|
-
constructor(submit: (type: MessageType) => void, NoopTimeFrequency?: number, NoopCountFrequency?: number);
|
|
12
|
-
/**
|
|
13
|
-
* Schedules as ack to the server to update the reference sequence number
|
|
14
|
-
*/
|
|
15
|
-
scheduleSequenceNumberUpdate(message: ISequencedDocumentMessage, immediateNoOp: boolean): void;
|
|
16
|
-
private submitNoop;
|
|
17
|
-
stopSequenceNumberUpdate(): void;
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=collabWindowTracker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"collabWindowTracker.d.ts","sourceRoot":"","sources":["../src/collabWindowTracker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAyB9F,qBAAa,mBAAmB;IAK9B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IANpC,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;gBAGxB,MAAM,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,EACpD,iBAAiB,GAAE,MAAiC,EACnC,kBAAkB,GAAE,MAAkC;IAaxE;;OAEG;IACI,4BAA4B,CAClC,OAAO,EAAE,yBAAyB,EAClC,aAAa,EAAE,OAAO,GACpB,IAAI;IAuCP,OAAO,CAAC,UAAU;IAUX,wBAAwB,IAAI,IAAI;CASvC"}
|