@fluidframework/container-loader 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.225277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +18 -21
- package/.mocharc.js +12 -0
- package/CHANGELOG.md +364 -0
- package/README.md +152 -56
- package/api-extractor-esm.json +4 -0
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +2 -2
- package/api-report/container-loader.api.md +143 -0
- package/dist/{audience.js → audience.cjs} +15 -13
- package/dist/audience.cjs.map +1 -0
- package/dist/audience.d.ts +4 -6
- package/dist/audience.d.ts.map +1 -1
- package/dist/catchUpMonitor.cjs +43 -0
- package/dist/catchUpMonitor.cjs.map +1 -0
- package/dist/catchUpMonitor.d.ts +29 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/{connectionManager.js → connectionManager.cjs} +397 -240
- package/dist/connectionManager.cjs.map +1 -0
- package/dist/connectionManager.d.ts +23 -33
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/{connectionState.js → connectionState.cjs} +5 -7
- package/dist/connectionState.cjs.map +1 -0
- package/dist/connectionState.d.ts +3 -5
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionStateHandler.cjs +474 -0
- package/dist/connectionStateHandler.cjs.map +1 -0
- package/dist/connectionStateHandler.d.ts +127 -29
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/container-loader-alpha.d.ts +274 -0
- package/dist/container-loader-beta.d.ts +75 -0
- package/dist/container-loader-public.d.ts +75 -0
- package/dist/container-loader-untrimmed.d.ts +331 -0
- package/dist/container.cjs +1585 -0
- package/dist/container.cjs.map +1 -0
- package/dist/container.d.ts +227 -83
- package/dist/container.d.ts.map +1 -1
- package/dist/containerContext.cjs +74 -0
- package/dist/containerContext.cjs.map +1 -0
- package/dist/containerContext.d.ts +33 -59
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerStorageAdapter.cjs +234 -0
- package/dist/containerStorageAdapter.cjs.map +1 -0
- package/dist/containerStorageAdapter.d.ts +48 -23
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/{contracts.js → contracts.cjs} +5 -5
- package/dist/contracts.cjs.map +1 -0
- package/dist/contracts.d.ts +45 -17
- package/dist/contracts.d.ts.map +1 -1
- package/dist/debugLogger.cjs +101 -0
- package/dist/debugLogger.cjs.map +1 -0
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/{deltaManager.js → deltaManager.cjs} +379 -186
- package/dist/deltaManager.cjs.map +1 -0
- package/dist/deltaManager.d.ts +54 -18
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/{deltaQueue.js → deltaQueue.cjs} +29 -28
- package/dist/deltaQueue.cjs.map +1 -0
- package/dist/deltaQueue.d.ts +3 -4
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/disposal.cjs +25 -0
- package/dist/disposal.cjs.map +1 -0
- package/dist/disposal.d.ts +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/error.cjs +32 -0
- package/dist/error.cjs.map +1 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/loader.cjs +148 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.ts +38 -19
- package/dist/loader.d.ts.map +1 -1
- package/dist/location-redirection-utilities/index.cjs +11 -0
- package/dist/location-redirection-utilities/index.cjs.map +1 -0
- package/dist/location-redirection-utilities/index.d.ts +6 -0
- package/dist/location-redirection-utilities/index.d.ts.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs +53 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +24 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
- package/dist/{collabWindowTracker.js → noopHeuristic.cjs} +37 -39
- package/dist/noopHeuristic.cjs.map +1 -0
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
- package/dist/packageVersion.cjs.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/protocol.cjs +99 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.ts +38 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.cjs} +8 -5
- package/dist/protocolTreeDocumentStorageService.cjs.map +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts +8 -4
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/quorum.cjs +16 -0
- package/dist/quorum.cjs.map +1 -0
- package/dist/quorum.d.ts +1 -14
- package/dist/quorum.d.ts.map +1 -1
- package/dist/{retriableDocumentStorageService.js → retriableDocumentStorageService.cjs} +36 -21
- package/dist/retriableDocumentStorageService.cjs.map +1 -0
- package/dist/retriableDocumentStorageService.d.ts +7 -5
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/{utils.js → utils.cjs} +52 -14
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.ts +34 -1
- package/dist/utils.d.ts.map +1 -1
- package/lib/{audience.d.ts → audience.d.mts} +5 -11
- package/lib/audience.d.mts.map +1 -0
- package/lib/{audience.js → audience.mjs} +15 -17
- package/lib/audience.mjs.map +1 -0
- package/lib/catchUpMonitor.d.mts +29 -0
- package/lib/catchUpMonitor.d.mts.map +1 -0
- package/lib/catchUpMonitor.mjs +39 -0
- package/lib/catchUpMonitor.mjs.map +1 -0
- package/lib/{connectionManager.d.ts → connectionManager.d.mts} +24 -34
- package/lib/connectionManager.d.mts.map +1 -0
- package/lib/{connectionManager.js → connectionManager.mjs} +378 -218
- package/lib/connectionManager.mjs.map +1 -0
- package/lib/{connectionState.d.ts → connectionState.d.mts} +4 -6
- package/lib/connectionState.d.mts.map +1 -0
- package/lib/{connectionState.js → connectionState.mjs} +4 -6
- package/lib/connectionState.mjs.map +1 -0
- package/lib/connectionStateHandler.d.mts +179 -0
- package/lib/connectionStateHandler.d.mts.map +1 -0
- package/lib/connectionStateHandler.mjs +469 -0
- package/lib/connectionStateHandler.mjs.map +1 -0
- package/lib/container-loader-alpha.d.mts +274 -0
- package/lib/container-loader-beta.d.mts +75 -0
- package/lib/container-loader-public.d.mts +75 -0
- package/lib/container-loader-untrimmed.d.mts +331 -0
- package/lib/container.d.mts +382 -0
- package/lib/container.d.mts.map +1 -0
- package/lib/container.mjs +1579 -0
- package/lib/container.mjs.map +1 -0
- package/lib/containerContext.d.mts +58 -0
- package/lib/containerContext.d.mts.map +1 -0
- package/lib/containerContext.mjs +70 -0
- package/lib/containerContext.mjs.map +1 -0
- package/lib/containerStorageAdapter.d.mts +73 -0
- package/lib/containerStorageAdapter.d.mts.map +1 -0
- package/lib/containerStorageAdapter.mjs +228 -0
- package/lib/containerStorageAdapter.mjs.map +1 -0
- package/lib/{contracts.d.ts → contracts.d.mts} +46 -18
- package/lib/contracts.d.mts.map +1 -0
- package/lib/{contracts.js → contracts.mjs} +4 -4
- package/lib/contracts.mjs.map +1 -0
- package/lib/debugLogger.d.mts +30 -0
- package/lib/debugLogger.d.mts.map +1 -0
- package/lib/debugLogger.mjs +93 -0
- package/lib/debugLogger.mjs.map +1 -0
- package/lib/{deltaManager.d.ts → deltaManager.d.mts} +55 -19
- package/lib/deltaManager.d.mts.map +1 -0
- package/lib/{deltaManager.js → deltaManager.mjs} +361 -165
- package/lib/deltaManager.mjs.map +1 -0
- package/lib/{deltaQueue.d.ts → deltaQueue.d.mts} +4 -5
- package/lib/deltaQueue.d.mts.map +1 -0
- package/lib/{deltaQueue.js → deltaQueue.mjs} +25 -24
- package/lib/deltaQueue.mjs.map +1 -0
- package/lib/disposal.d.mts +13 -0
- package/lib/disposal.d.mts.map +1 -0
- package/lib/disposal.mjs +21 -0
- package/lib/disposal.mjs.map +1 -0
- package/lib/error.d.mts +23 -0
- package/lib/error.d.mts.map +1 -0
- package/lib/error.mjs +28 -0
- package/lib/error.mjs.map +1 -0
- package/lib/index.d.mts +11 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +10 -0
- package/lib/index.mjs.map +1 -0
- package/lib/{loader.d.ts → loader.d.mts} +40 -21
- package/lib/loader.d.mts.map +1 -0
- package/lib/loader.mjs +143 -0
- package/lib/loader.mjs.map +1 -0
- package/lib/location-redirection-utilities/index.d.mts +6 -0
- package/lib/location-redirection-utilities/index.d.mts.map +1 -0
- package/lib/location-redirection-utilities/index.mjs +6 -0
- package/lib/location-redirection-utilities/index.mjs.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts +24 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs +48 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs.map +1 -0
- package/lib/noopHeuristic.d.mts +23 -0
- package/lib/noopHeuristic.d.mts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.mjs} +33 -35
- package/lib/noopHeuristic.mjs.map +1 -0
- package/lib/{packageVersion.d.ts → packageVersion.d.mts} +2 -2
- package/lib/packageVersion.d.mts.map +1 -0
- package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
- package/lib/packageVersion.mjs.map +1 -0
- package/lib/protocol.d.mts +38 -0
- package/lib/protocol.d.mts.map +1 -0
- package/lib/protocol.mjs +94 -0
- package/lib/protocol.mjs.map +1 -0
- package/lib/{protocolTreeDocumentStorageService.d.ts → protocolTreeDocumentStorageService.d.mts} +9 -5
- package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -0
- package/lib/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.mjs} +8 -5
- package/lib/protocolTreeDocumentStorageService.mjs.map +1 -0
- package/lib/quorum.d.mts +4 -0
- package/lib/quorum.d.mts.map +1 -0
- package/lib/quorum.mjs +12 -0
- package/lib/quorum.mjs.map +1 -0
- package/lib/{retriableDocumentStorageService.d.ts → retriableDocumentStorageService.d.mts} +8 -6
- package/lib/retriableDocumentStorageService.d.mts.map +1 -0
- package/lib/{retriableDocumentStorageService.js → retriableDocumentStorageService.mjs} +35 -20
- package/lib/retriableDocumentStorageService.mjs.map +1 -0
- package/lib/utils.d.mts +67 -0
- package/lib/utils.d.mts.map +1 -0
- package/lib/{utils.js → utils.mjs} +47 -11
- package/lib/utils.mjs.map +1 -0
- package/package.json +189 -69
- package/prettier.config.cjs +8 -0
- package/src/audience.ts +59 -49
- package/src/catchUpMonitor.ts +61 -0
- package/src/connectionManager.ts +1154 -910
- package/src/connectionState.ts +22 -25
- package/src/connectionStateHandler.ts +689 -319
- package/src/container.ts +2476 -1792
- package/src/containerContext.ts +98 -330
- package/src/containerStorageAdapter.ts +301 -105
- package/src/contracts.ts +184 -146
- package/src/debugLogger.ts +123 -0
- package/src/deltaManager.ts +1165 -900
- package/src/deltaQueue.ts +156 -152
- package/src/disposal.ts +25 -0
- package/src/error.ts +44 -0
- package/src/index.ts +14 -15
- package/src/loader.ts +356 -427
- package/src/location-redirection-utilities/index.ts +9 -0
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +61 -0
- package/src/noopHeuristic.ts +107 -0
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +150 -0
- package/src/protocolTreeDocumentStorageService.ts +35 -35
- package/src/quorum.ts +11 -50
- package/src/retriableDocumentStorageService.ts +135 -95
- package/src/utils.ts +159 -86
- package/tsc-multi.test.json +4 -0
- package/tsconfig.json +10 -12
- package/dist/audience.js.map +0 -1
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js.map +0 -1
- package/dist/connectionManager.js.map +0 -1
- package/dist/connectionState.js.map +0 -1
- package/dist/connectionStateHandler.js +0 -280
- package/dist/connectionStateHandler.js.map +0 -1
- package/dist/container.js +0 -1284
- package/dist/container.js.map +0 -1
- package/dist/containerContext.js +0 -217
- package/dist/containerContext.js.map +0 -1
- package/dist/containerStorageAdapter.js +0 -104
- package/dist/containerStorageAdapter.js.map +0 -1
- package/dist/contracts.js.map +0 -1
- package/dist/deltaManager.js.map +0 -1
- package/dist/deltaManagerProxy.d.ts +0 -54
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -115
- package/dist/deltaManagerProxy.js.map +0 -1
- package/dist/deltaQueue.js.map +0 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +0 -1
- package/dist/loader.js +0 -241
- package/dist/loader.js.map +0 -1
- package/dist/packageVersion.js.map +0 -1
- package/dist/protocolTreeDocumentStorageService.js.map +0 -1
- package/dist/quorum.js +0 -44
- package/dist/quorum.js.map +0 -1
- package/dist/retriableDocumentStorageService.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/lib/audience.d.ts.map +0 -1
- package/lib/audience.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/connectionManager.d.ts.map +0 -1
- package/lib/connectionManager.js.map +0 -1
- package/lib/connectionState.d.ts.map +0 -1
- package/lib/connectionState.js.map +0 -1
- package/lib/connectionStateHandler.d.ts +0 -81
- package/lib/connectionStateHandler.d.ts.map +0 -1
- package/lib/connectionStateHandler.js +0 -276
- package/lib/connectionStateHandler.js.map +0 -1
- package/lib/container.d.ts +0 -238
- package/lib/container.d.ts.map +0 -1
- package/lib/container.js +0 -1276
- package/lib/container.js.map +0 -1
- package/lib/containerContext.d.ts +0 -84
- package/lib/containerContext.d.ts.map +0 -1
- package/lib/containerContext.js +0 -213
- package/lib/containerContext.js.map +0 -1
- package/lib/containerStorageAdapter.d.ts +0 -48
- package/lib/containerStorageAdapter.d.ts.map +0 -1
- package/lib/containerStorageAdapter.js +0 -99
- package/lib/containerStorageAdapter.js.map +0 -1
- package/lib/contracts.d.ts.map +0 -1
- package/lib/contracts.js.map +0 -1
- package/lib/deltaManager.d.ts.map +0 -1
- package/lib/deltaManager.js.map +0 -1
- package/lib/deltaManagerProxy.d.ts +0 -54
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -110
- package/lib/deltaManagerProxy.js.map +0 -1
- package/lib/deltaQueue.d.ts.map +0 -1
- package/lib/deltaQueue.js.map +0 -1
- package/lib/index.d.ts +0 -8
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -8
- package/lib/index.js.map +0 -1
- package/lib/loader.d.ts.map +0 -1
- package/lib/loader.js +0 -236
- package/lib/loader.js.map +0 -1
- package/lib/packageVersion.d.ts.map +0 -1
- package/lib/packageVersion.js.map +0 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +0 -1
- package/lib/protocolTreeDocumentStorageService.js.map +0 -1
- package/lib/quorum.d.ts +0 -21
- package/lib/quorum.d.ts.map +0 -1
- package/lib/quorum.js +0 -38
- package/lib/quorum.js.map +0 -1
- package/lib/retriableDocumentStorageService.d.ts.map +0 -1
- package/lib/retriableDocumentStorageService.js.map +0 -1
- package/lib/utils.d.ts +0 -34
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js.map +0 -1
- package/src/collabWindowTracker.ts +0 -102
- package/src/deltaManagerProxy.ts +0 -158
- package/tsconfig.esnext.json +0 -7
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ITelemetryBaseLogger, IRequest } from "@fluidframework/core-interfaces";
|
|
7
|
+
import {
|
|
8
|
+
DriverErrorTypes,
|
|
9
|
+
ILocationRedirectionError,
|
|
10
|
+
IUrlResolver,
|
|
11
|
+
} from "@fluidframework/driver-definitions";
|
|
12
|
+
import { createChildLogger } from "@fluidframework/telemetry-utils";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the error is location redirection error.
|
|
16
|
+
* @param error - error whose type is to be determined.
|
|
17
|
+
* @returns `true` is the error is location redirection error, otherwise `false`.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function isLocationRedirectionError(error: any): error is ILocationRedirectionError {
|
|
21
|
+
return (
|
|
22
|
+
typeof error === "object" &&
|
|
23
|
+
error !== null &&
|
|
24
|
+
error.errorType === DriverErrorTypes.locationRedirection
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Handles location redirection while fulfilling the loader request.
|
|
30
|
+
* @param api - Callback in which user can wrap the loader.resolve or loader.request call.
|
|
31
|
+
* @param request - request to be resolved.
|
|
32
|
+
* @param urlResolver - resolver used to resolve the url.
|
|
33
|
+
* @param logger - logger to send events.
|
|
34
|
+
* @returns Response from the API call.
|
|
35
|
+
* @alpha
|
|
36
|
+
*/
|
|
37
|
+
export async function resolveWithLocationRedirectionHandling<T>(
|
|
38
|
+
api: (request: IRequest) => Promise<T>,
|
|
39
|
+
request: IRequest,
|
|
40
|
+
urlResolver: IUrlResolver,
|
|
41
|
+
logger?: ITelemetryBaseLogger,
|
|
42
|
+
): Promise<T> {
|
|
43
|
+
let req: IRequest = request;
|
|
44
|
+
const childLogger = createChildLogger({ logger, namespace: "LocationRedirection" });
|
|
45
|
+
for (;;) {
|
|
46
|
+
try {
|
|
47
|
+
return await api(req);
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
if (!isLocationRedirectionError(error)) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
childLogger.sendTelemetryEvent({ eventName: "LocationRedirectionError" });
|
|
53
|
+
const resolvedUrl = error.redirectUrl;
|
|
54
|
+
// Generate the new request with new location details from the resolved url. For datastore/relative path,
|
|
55
|
+
// we don't need to pass "/" as host could have asked for a specific data store. So driver need to
|
|
56
|
+
// extract it from the resolved url.
|
|
57
|
+
const absoluteUrl = await urlResolver.getAbsoluteUrl(resolvedUrl, "", undefined);
|
|
58
|
+
req = { url: absoluteUrl, headers: req.headers };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
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";
|
|
11
|
+
|
|
12
|
+
const defaultNoopTimeFrequency = 2000;
|
|
13
|
+
const defaultNoopCountFrequency = 50;
|
|
14
|
+
|
|
15
|
+
export interface INoopSenderEvents extends IEvent {
|
|
16
|
+
(event: "wantsNoop", listener: () => void);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Here are key considerations when deciding conditions for when to send non-immediate noops:
|
|
20
|
+
// 1. Sending them too often results in increase in file size and bandwidth, as well as catch up performance
|
|
21
|
+
// 2. Sending too infrequently ensures that collab window is large, and as result Sequence DDS would have
|
|
22
|
+
// large catchUp blobs - see Issue #6364
|
|
23
|
+
// 3. Similarly, processes that rely on "core" snapshot (and can't parse trailing ops, including above), like search
|
|
24
|
+
// parser in SPO, will result in non-accurate results due to presence of catch up blobs.
|
|
25
|
+
// 4. Ordering service used 250ms timeout to coalesce non-immediate noops. It was changed to 2000 ms to allow more
|
|
26
|
+
// aggressive noop sending from client side.
|
|
27
|
+
// 5. Number of ops sent by all clients is proportional to number of "write" clients (every client sends noops),
|
|
28
|
+
// but number of sequenced noops is a function of time (one op per 2 seconds at most).
|
|
29
|
+
// We should consider impact to both outbound traffic (might be huge, depends on number of clients) and file size.
|
|
30
|
+
// Please also see Issue #5629 for more discussions.
|
|
31
|
+
//
|
|
32
|
+
// With that, the current algorithm is as follows:
|
|
33
|
+
// 1. Sent noop 2000 ms of receiving an op if no ops were sent by this client within this timeframe.
|
|
34
|
+
// This will ensure that MSN moves forward with reasonable speed. If that results in too many sequenced noops,
|
|
35
|
+
// server timeout of 2000ms should be reconsidered to be increased.
|
|
36
|
+
// 2. If there are more than 50 ops received without sending any ops, send noop to keep collab window small.
|
|
37
|
+
// Note that system ops (including noops themselves) are excluded, so it's 1 noop per 50 real ops.
|
|
38
|
+
export class NoopHeuristic extends TypedEventEmitter<INoopSenderEvents> {
|
|
39
|
+
private opsProcessedSinceOpSent = 0;
|
|
40
|
+
private readonly timer: Timer | undefined;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
NoopTimeFrequency: number = defaultNoopTimeFrequency,
|
|
44
|
+
private readonly NoopCountFrequency: number = defaultNoopCountFrequency,
|
|
45
|
+
) {
|
|
46
|
+
super();
|
|
47
|
+
if (NoopTimeFrequency !== Infinity) {
|
|
48
|
+
this.timer = new Timer(NoopTimeFrequency, () => {
|
|
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");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Schedules as ack to the server to update the reference sequence number
|
|
60
|
+
*/
|
|
61
|
+
public notifyMessageProcessed(message: ISequencedDocumentMessage): void {
|
|
62
|
+
// We don't acknowledge no-ops to avoid acknowledgement cycles (i.e. ack the MSN
|
|
63
|
+
// update, which updates the MSN, then ack the update, etc...).
|
|
64
|
+
// Intent here is for runtime (and DDSes) not to keep too much tracking state / memory
|
|
65
|
+
// due to runtime ops from other clients.
|
|
66
|
+
if (!isRuntimeMessage(message)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
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
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.timer !== undefined) {
|
|
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) {
|
|
91
|
+
this.timer.restart();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
assert(this.timer.hasTimer, 0x242 /* "has timer" */);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
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;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public notifyMessageSent(): void {
|
|
104
|
+
// Sending any message is as good as a noop for updating MSN.
|
|
105
|
+
this.opsProcessedSinceOpSent = 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IAudienceOwner } from "@fluidframework/container-definitions";
|
|
7
|
+
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
8
|
+
import {
|
|
9
|
+
IProtocolHandler as IBaseProtocolHandler,
|
|
10
|
+
IQuorumSnapshot,
|
|
11
|
+
ProtocolOpHandler,
|
|
12
|
+
} from "@fluidframework/protocol-base";
|
|
13
|
+
import {
|
|
14
|
+
IDocumentAttributes,
|
|
15
|
+
IProcessMessageResult,
|
|
16
|
+
ISequencedDocumentMessage,
|
|
17
|
+
ISignalClient,
|
|
18
|
+
ISignalMessage,
|
|
19
|
+
MessageType,
|
|
20
|
+
} from "@fluidframework/protocol-definitions";
|
|
21
|
+
|
|
22
|
+
// ADO: #1986: Start using enum from protocol-base.
|
|
23
|
+
export enum SignalType {
|
|
24
|
+
ClientJoin = "join", // same value as MessageType.ClientJoin,
|
|
25
|
+
ClientLeave = "leave", // same value as MessageType.ClientLeave,
|
|
26
|
+
Clear = "clear", // used only by client for synthetic signals
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Function to be used for creating a protocol handler.
|
|
31
|
+
* @alpha
|
|
32
|
+
*/
|
|
33
|
+
export type ProtocolHandlerBuilder = (
|
|
34
|
+
attributes: IDocumentAttributes,
|
|
35
|
+
snapshot: IQuorumSnapshot,
|
|
36
|
+
sendProposal: (key: string, value: any) => number,
|
|
37
|
+
) => IProtocolHandler;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @alpha
|
|
41
|
+
*/
|
|
42
|
+
export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
43
|
+
readonly audience: IAudienceOwner;
|
|
44
|
+
processSignal(message: ISignalMessage);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
|
|
48
|
+
constructor(
|
|
49
|
+
attributes: IDocumentAttributes,
|
|
50
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
51
|
+
sendProposal: (key: string, value: any) => number,
|
|
52
|
+
public readonly audience: IAudienceOwner,
|
|
53
|
+
private readonly shouldClientHaveLeft: (clientId: string) => boolean,
|
|
54
|
+
) {
|
|
55
|
+
super(
|
|
56
|
+
attributes.minimumSequenceNumber,
|
|
57
|
+
attributes.sequenceNumber,
|
|
58
|
+
quorumSnapshot.members,
|
|
59
|
+
quorumSnapshot.proposals,
|
|
60
|
+
quorumSnapshot.values,
|
|
61
|
+
sendProposal,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Join / leave signals are ignored for "write" clients in favor of join / leave ops
|
|
65
|
+
this.quorum.on("addMember", (clientId, details) =>
|
|
66
|
+
audience.addMember(clientId, details.client),
|
|
67
|
+
);
|
|
68
|
+
this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
|
|
69
|
+
for (const [clientId, details] of this.quorum.getMembers()) {
|
|
70
|
+
this.audience.addMember(clientId, details.client);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public processMessage(
|
|
75
|
+
message: ISequencedDocumentMessage,
|
|
76
|
+
local: boolean,
|
|
77
|
+
): IProcessMessageResult {
|
|
78
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
79
|
+
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
80
|
+
if (message.clientId != null) {
|
|
81
|
+
const client = this.quorum.getMember(message.clientId);
|
|
82
|
+
|
|
83
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
84
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
85
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
86
|
+
}
|
|
87
|
+
|
|
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)) {
|
|
92
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
93
|
+
throw new Error("Remote message's clientId already should have left");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return super.processMessage(message, local);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public processSignal(message: ISignalMessage) {
|
|
101
|
+
const innerContent = message.content as { content: any; type: string };
|
|
102
|
+
switch (innerContent.type) {
|
|
103
|
+
case SignalType.Clear: {
|
|
104
|
+
const members = this.audience.getMembers();
|
|
105
|
+
for (const [clientId, client] of members) {
|
|
106
|
+
if (client.mode === "read") {
|
|
107
|
+
this.audience.removeMember(clientId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case SignalType.ClientJoin: {
|
|
113
|
+
const newClient = innerContent.content as ISignalClient;
|
|
114
|
+
// Ignore write clients - quorum will control such clients.
|
|
115
|
+
if (newClient.client.mode === "read") {
|
|
116
|
+
this.audience.addMember(newClient.clientId, newClient.client);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case SignalType.ClientLeave: {
|
|
121
|
+
const leftClientId = innerContent.content as string;
|
|
122
|
+
// Ignore write clients - quorum will control such clients.
|
|
123
|
+
if (this.audience.getMember(leftClientId)?.mode === "read") {
|
|
124
|
+
this.audience.removeMember(leftClientId);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
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,43 +3,43 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IDisposable } from "@fluidframework/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
ISummaryContext,
|
|
10
|
-
} from "@fluidframework/driver-definitions";
|
|
11
|
-
import { combineAppAndProtocolSummary } from "@fluidframework/driver-utils";
|
|
12
|
-
import {
|
|
13
|
-
ISummaryTree,
|
|
14
|
-
} from "@fluidframework/protocol-definitions";
|
|
6
|
+
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
|
+
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
8
|
+
import { ISummaryTree } from "@fluidframework/protocol-definitions";
|
|
15
9
|
|
|
10
|
+
/**
|
|
11
|
+
* A storage service wrapper whose sole job is to intercept calls to uploadSummaryWithContext and ensure they include
|
|
12
|
+
* the protocol summary, using the provided callback to add it if necessary.
|
|
13
|
+
*/
|
|
16
14
|
export class ProtocolTreeStorageService implements IDocumentStorageService, IDisposable {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly internalStorageService: IDocumentStorageService & IDisposable,
|
|
17
|
+
private readonly addProtocolSummaryIfMissing: (summaryTree: ISummaryTree) => ISummaryTree,
|
|
18
|
+
) {}
|
|
19
|
+
public get policies() {
|
|
20
|
+
return this.internalStorageService.policies;
|
|
21
|
+
}
|
|
22
|
+
public get repositoryUrl() {
|
|
23
|
+
return this.internalStorageService.repositoryUrl;
|
|
24
|
+
}
|
|
25
|
+
public get disposed() {
|
|
26
|
+
return this.internalStorageService.disposed;
|
|
27
|
+
}
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
|
|
30
|
+
getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
|
|
31
|
+
createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
|
|
32
|
+
readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
|
|
33
|
+
downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
|
|
34
|
+
dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
async uploadSummaryWithContext(
|
|
37
|
+
summary: ISummaryTree,
|
|
38
|
+
context: ISummaryContext,
|
|
39
|
+
): Promise<string> {
|
|
40
|
+
return this.internalStorageService.uploadSummaryWithContext(
|
|
41
|
+
this.addProtocolSummaryIfMissing(summary),
|
|
42
|
+
context,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
45
|
}
|
package/src/quorum.ts
CHANGED
|
@@ -2,58 +2,19 @@
|
|
|
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,
|
|
49
10
|
): [string, ICommittedProposal][] {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
11
|
+
// Seed the base quorum to be an empty list with a code quorum set
|
|
12
|
+
const committedCodeProposal: ICommittedProposal = {
|
|
13
|
+
key: "code",
|
|
14
|
+
value: source,
|
|
15
|
+
approvalSequenceNumber: 0,
|
|
16
|
+
commitSequenceNumber: 0,
|
|
17
|
+
sequenceNumber: 0,
|
|
18
|
+
};
|
|
19
|
+
return [["code", committedCodeProposal]];
|
|
59
20
|
}
|