@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
|
@@ -2,97 +2,64 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import { default as AbortController } from "abort-controller";
|
|
6
5
|
import { v4 as uuid } from "uuid";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
7
|
+
import { assert } from "@fluidframework/core-utils";
|
|
8
|
+
import { DataProcessingError, extractSafePropertiesFromMessage, normalizeError, safeRaiseEvent, isFluidError, DataCorruptionError, UsageError, } from "@fluidframework/telemetry-utils";
|
|
9
|
+
import { DriverErrorTypes, } from "@fluidframework/driver-definitions";
|
|
10
10
|
import { MessageType, } from "@fluidframework/protocol-definitions";
|
|
11
|
-
import { NonRetryableError,
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { NonRetryableError, isRuntimeMessage, MessageType2 } from "@fluidframework/driver-utils";
|
|
12
|
+
import { DeltaQueue } from "./deltaQueue.mjs";
|
|
13
|
+
import { ThrottlingWarning } from "./error.mjs";
|
|
14
|
+
/**
|
|
15
|
+
* Determines if message was sent by client, not service
|
|
16
|
+
*/
|
|
17
|
+
function isClientMessage(message) {
|
|
18
|
+
if (isRuntimeMessage(message)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
switch (message.type) {
|
|
22
|
+
case MessageType.Propose:
|
|
23
|
+
case MessageType.Reject:
|
|
24
|
+
case MessageType.NoOp:
|
|
25
|
+
case MessageType2.Accept:
|
|
26
|
+
case MessageType.Summarize:
|
|
27
|
+
return true;
|
|
28
|
+
default:
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Like assert, but logs only if the condition is false, rather than throwing
|
|
34
|
+
* @param condition - The condition to attest too
|
|
35
|
+
* @param logger - The logger to log with
|
|
36
|
+
* @param event - The string or event to log
|
|
37
|
+
* @returns The outcome of the condition
|
|
38
|
+
*/
|
|
39
|
+
function logIfFalse(condition, logger, event) {
|
|
40
|
+
if (condition) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const newEvent = typeof event === "string"
|
|
44
|
+
? { eventName: event, category: "error" }
|
|
45
|
+
: { category: "error", ...event };
|
|
46
|
+
logger.send(newEvent);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
14
49
|
/**
|
|
15
50
|
* Manages the flow of both inbound and outbound messages. This class ensures that shared objects receive delta
|
|
16
51
|
* messages in order regardless of possible network conditions or timings causing out of order delivery.
|
|
17
52
|
*/
|
|
18
53
|
export class DeltaManager extends TypedEventEmitter {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this
|
|
27
|
-
// There are three numbers we track
|
|
28
|
-
// * lastQueuedSequenceNumber is the last queued sequence number. If there are gaps in seq numbers, then this number
|
|
29
|
-
// is not updated until we cover that gap, so it increases each time by 1.
|
|
30
|
-
// * lastObservedSeqNumber is an estimation of last known sequence number for container in storage. It's initially
|
|
31
|
-
// populated at web socket connection time (if storage provides that info) and is updated once ops shows up.
|
|
32
|
-
// It's never less than lastQueuedSequenceNumber
|
|
33
|
-
// * lastProcessedSequenceNumber - last processed sequence number
|
|
34
|
-
this.lastQueuedSequenceNumber = 0;
|
|
35
|
-
this.lastObservedSeqNumber = 0;
|
|
36
|
-
this.lastProcessedSequenceNumber = 0;
|
|
37
|
-
this.baseTerm = 0;
|
|
38
|
-
/**
|
|
39
|
-
* Track down the ops size.
|
|
40
|
-
*/
|
|
41
|
-
this.opsSize = 0;
|
|
42
|
-
// The sequence number we initially loaded from
|
|
43
|
-
this.initSequenceNumber = 0;
|
|
44
|
-
this.closed = false;
|
|
45
|
-
this.throttlingIdSet = new Set();
|
|
46
|
-
this.timeTillThrottling = 0;
|
|
47
|
-
this.closeAbortController = new AbortController();
|
|
48
|
-
this.deltaStorageDelayId = uuid();
|
|
49
|
-
this.deltaStreamDelayId = uuid();
|
|
50
|
-
this.messageBuffer = [];
|
|
51
|
-
const props = {
|
|
52
|
-
incomingOpHandler: (messages, reason) => {
|
|
53
|
-
try {
|
|
54
|
-
this.enqueueMessages(messages, reason);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
this.logger.sendErrorEvent({ eventName: "EnqueueMessages_Exception" }, error);
|
|
58
|
-
this.close(normalizeError(error));
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
signalHandler: (message) => this._inboundSignal.push(message),
|
|
62
|
-
reconnectionDelayHandler: (delayMs, error) => this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
|
|
63
|
-
closeHandler: (error) => this.close(error),
|
|
64
|
-
disconnectHandler: (reason) => this.disconnectHandler(reason),
|
|
65
|
-
connectHandler: (connection) => this.connectHandler(connection),
|
|
66
|
-
pongHandler: (latency) => this.emit("pong", latency),
|
|
67
|
-
readonlyChangeHandler: (readonly) => safeRaiseEvent(this, this.logger, "readonly", readonly),
|
|
68
|
-
};
|
|
69
|
-
this.connectionManager = createConnectionManager(props);
|
|
70
|
-
this._inbound = new DeltaQueue((op) => {
|
|
71
|
-
this.processInboundMessage(op);
|
|
72
|
-
});
|
|
73
|
-
this._inbound.on("error", (error) => {
|
|
74
|
-
this.close(DataProcessingError.wrapIfUnrecognized(error, "deltaManagerInboundErrorHandler", this.lastMessage));
|
|
75
|
-
});
|
|
76
|
-
// Inbound signal queue
|
|
77
|
-
this._inboundSignal = new DeltaQueue((message) => {
|
|
78
|
-
if (this.handler === undefined) {
|
|
79
|
-
throw new Error("Attempted to process an inbound signal without a handler attached");
|
|
80
|
-
}
|
|
81
|
-
this.handler.processSignal({
|
|
82
|
-
clientId: message.clientId,
|
|
83
|
-
content: JSON.parse(message.content),
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
this._inboundSignal.on("error", (error) => {
|
|
87
|
-
this.close(normalizeError(error));
|
|
88
|
-
});
|
|
89
|
-
// Initially, all queues are created paused.
|
|
90
|
-
// - outbound is flipped back and forth in setupNewSuccessfulConnection / disconnectFromDeltaStream
|
|
91
|
-
// - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
|
|
54
|
+
get active() {
|
|
55
|
+
return this._active();
|
|
56
|
+
}
|
|
57
|
+
get disposed() {
|
|
58
|
+
return this._closed;
|
|
59
|
+
}
|
|
60
|
+
get IDeltaSender() {
|
|
61
|
+
return this;
|
|
92
62
|
}
|
|
93
|
-
get active() { return this._active(); }
|
|
94
|
-
get disposed() { return this.closed; }
|
|
95
|
-
get IDeltaSender() { return this; }
|
|
96
63
|
get inbound() {
|
|
97
64
|
return this._inbound;
|
|
98
65
|
}
|
|
@@ -111,9 +78,6 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
111
78
|
get lastKnownSeqNumber() {
|
|
112
79
|
return this.lastObservedSeqNumber;
|
|
113
80
|
}
|
|
114
|
-
get referenceTerm() {
|
|
115
|
-
return this.baseTerm;
|
|
116
|
-
}
|
|
117
81
|
get minimumSequenceNumber() {
|
|
118
82
|
return this.minSequenceNumber;
|
|
119
83
|
}
|
|
@@ -127,18 +91,33 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
127
91
|
return this._checkpointSequenceNumber !== undefined;
|
|
128
92
|
}
|
|
129
93
|
// Forwarding connection manager properties / IDeltaManager implementation
|
|
130
|
-
get maxMessageSize() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
get
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
94
|
+
get maxMessageSize() {
|
|
95
|
+
return this.connectionManager.maxMessageSize;
|
|
96
|
+
}
|
|
97
|
+
get version() {
|
|
98
|
+
return this.connectionManager.version;
|
|
99
|
+
}
|
|
100
|
+
get serviceConfiguration() {
|
|
101
|
+
return this.connectionManager.serviceConfiguration;
|
|
102
|
+
}
|
|
103
|
+
get outbound() {
|
|
104
|
+
return this.connectionManager.outbound;
|
|
105
|
+
}
|
|
106
|
+
get readOnlyInfo() {
|
|
107
|
+
return this.connectionManager.readOnlyInfo;
|
|
108
|
+
}
|
|
109
|
+
get clientDetails() {
|
|
110
|
+
return this.connectionManager.clientDetails;
|
|
111
|
+
}
|
|
112
|
+
submit(type, contents, batch = false, metadata, compression, referenceSequenceNumber) {
|
|
113
|
+
// Back-compat ADO:3455
|
|
114
|
+
const backCompatRefSeqNum = referenceSequenceNumber ?? this.lastProcessedSequenceNumber;
|
|
137
115
|
const messagePartial = {
|
|
138
|
-
contents
|
|
116
|
+
contents,
|
|
139
117
|
metadata,
|
|
140
|
-
referenceSequenceNumber:
|
|
118
|
+
referenceSequenceNumber: backCompatRefSeqNum,
|
|
141
119
|
type,
|
|
120
|
+
compression,
|
|
142
121
|
};
|
|
143
122
|
if (!batch) {
|
|
144
123
|
this.flush();
|
|
@@ -147,26 +126,48 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
147
126
|
if (message === undefined) {
|
|
148
127
|
return -1;
|
|
149
128
|
}
|
|
150
|
-
|
|
129
|
+
assert(isClientMessage(message), 0x419 /* client sends non-client message */);
|
|
130
|
+
if (contents !== undefined) {
|
|
131
|
+
this.opsSize += contents.length;
|
|
132
|
+
}
|
|
151
133
|
this.messageBuffer.push(message);
|
|
134
|
+
if (message.type === MessageType.NoOp) {
|
|
135
|
+
this.noOpCount++;
|
|
136
|
+
}
|
|
152
137
|
this.emit("submitOp", message);
|
|
153
138
|
if (!batch) {
|
|
154
139
|
this.flush();
|
|
155
140
|
}
|
|
156
141
|
return message.clientSequenceNumber;
|
|
157
142
|
}
|
|
158
|
-
submitSignal(content) {
|
|
143
|
+
submitSignal(content, targetClientId) {
|
|
144
|
+
return this.connectionManager.submitSignal(content, targetClientId);
|
|
145
|
+
}
|
|
159
146
|
flush() {
|
|
160
|
-
|
|
147
|
+
const batch = this.messageBuffer;
|
|
148
|
+
if (batch.length === 0) {
|
|
161
149
|
return;
|
|
162
150
|
}
|
|
163
|
-
// The prepareFlush event allows listeners to append metadata to the batch prior to submission.
|
|
164
|
-
this.emit("prepareSend", this.messageBuffer);
|
|
165
|
-
this.connectionManager.sendMessages(this.messageBuffer);
|
|
166
151
|
this.messageBuffer = [];
|
|
152
|
+
// The prepareFlush event allows listeners to append metadata to the batch prior to submission.
|
|
153
|
+
this.emit("prepareSend", batch);
|
|
154
|
+
if (batch.length === 1) {
|
|
155
|
+
assert(batch[0].metadata?.batch === undefined, 0x3c9 /* no batch markup on single message */);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
assert(batch[0].metadata?.batch === true, 0x3ca /* no start batch markup */);
|
|
159
|
+
assert(batch[batch.length - 1].metadata?.batch === false, 0x3cb /* no end batch markup */);
|
|
160
|
+
}
|
|
161
|
+
this.connectionManager.sendMessages(batch);
|
|
162
|
+
assert(this.messageBuffer.length === 0, 0x3cc /* reentrancy */);
|
|
167
163
|
}
|
|
168
164
|
get connectionProps() {
|
|
169
|
-
return
|
|
165
|
+
return {
|
|
166
|
+
sequenceNumber: this.lastSequenceNumber,
|
|
167
|
+
opsSize: this.opsSize > 0 ? this.opsSize : undefined,
|
|
168
|
+
deltaManagerState: this._disposed ? "disposed" : this._closed ? "closed" : "open",
|
|
169
|
+
...this.connectionManager.connectionProps,
|
|
170
|
+
};
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
172
173
|
* Log error event with a bunch of internal to DeltaManager information about state of op processing
|
|
@@ -175,15 +176,121 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
175
176
|
* @param event - Event to log.
|
|
176
177
|
*/
|
|
177
178
|
logConnectionIssue(event) {
|
|
178
|
-
var _a;
|
|
179
179
|
assert(this.connectionManager.connected, 0x238 /* "called only in connected state" */);
|
|
180
180
|
const pendingSorted = this.pending.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
181
|
-
this.logger.
|
|
181
|
+
this.logger.sendTelemetryEvent({
|
|
182
|
+
...event,
|
|
182
183
|
// This directly tells us if fetching ops is in flight, and thus likely the reason of
|
|
183
184
|
// stalled op processing
|
|
184
|
-
fetchReason: this.fetchReason,
|
|
185
|
+
fetchReason: this.fetchReason,
|
|
185
186
|
// A bunch of useful sequence numbers to understand if we are holding some ops from processing
|
|
186
|
-
lastQueuedSequenceNumber: this.lastQueuedSequenceNumber,
|
|
187
|
+
lastQueuedSequenceNumber: this.lastQueuedSequenceNumber,
|
|
188
|
+
lastProcessedSequenceNumber: this.lastProcessedSequenceNumber,
|
|
189
|
+
lastObserved: this.lastObservedSeqNumber,
|
|
190
|
+
// connection info
|
|
191
|
+
...this.connectionManager.connectionVerboseProps,
|
|
192
|
+
pendingOps: this.pending.length,
|
|
193
|
+
pendingFirst: pendingSorted[0]?.sequenceNumber,
|
|
194
|
+
haveHandler: this.handler !== undefined,
|
|
195
|
+
inboundLength: this.inbound.length,
|
|
196
|
+
inboundPaused: this.inbound.paused,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
constructor(serviceProvider, logger, _active, createConnectionManager) {
|
|
200
|
+
super();
|
|
201
|
+
this.serviceProvider = serviceProvider;
|
|
202
|
+
this.logger = logger;
|
|
203
|
+
this._active = _active;
|
|
204
|
+
this.pending = [];
|
|
205
|
+
// A boolean used to assert that ops are not being sent while processing another op.
|
|
206
|
+
this.currentlyProcessingOps = false;
|
|
207
|
+
// The minimum sequence number and last sequence number received from the server
|
|
208
|
+
this.minSequenceNumber = 0;
|
|
209
|
+
// There are three numbers we track
|
|
210
|
+
// * lastQueuedSequenceNumber is the last queued sequence number. If there are gaps in seq numbers, then this number
|
|
211
|
+
// is not updated until we cover that gap, so it increases each time by 1.
|
|
212
|
+
// * lastObservedSeqNumber is an estimation of last known sequence number for container in storage. It's initially
|
|
213
|
+
// populated at web socket connection time (if storage provides that info) and is updated once ops shows up.
|
|
214
|
+
// It's never less than lastQueuedSequenceNumber
|
|
215
|
+
// * lastProcessedSequenceNumber - last processed sequence number
|
|
216
|
+
this.lastQueuedSequenceNumber = 0;
|
|
217
|
+
this.lastObservedSeqNumber = 0;
|
|
218
|
+
this.lastProcessedSequenceNumber = 0;
|
|
219
|
+
/** count number of noops sent by the client which may not be acked */
|
|
220
|
+
this.noOpCount = 0;
|
|
221
|
+
/** Track clientSequenceNumber of the last op */
|
|
222
|
+
this.lastClientSequenceNumber = 0;
|
|
223
|
+
/**
|
|
224
|
+
* Track down the ops size.
|
|
225
|
+
*/
|
|
226
|
+
this.opsSize = 0;
|
|
227
|
+
// The sequence number we initially loaded from
|
|
228
|
+
// In case of reading from a snapshot or pending state, its value will be equal to
|
|
229
|
+
// the last message that got serialized.
|
|
230
|
+
this.initSequenceNumber = 0;
|
|
231
|
+
this._closed = false;
|
|
232
|
+
this._disposed = false;
|
|
233
|
+
this.throttlingIdSet = new Set();
|
|
234
|
+
this.timeTillThrottling = 0;
|
|
235
|
+
this.closeAbortController = new AbortController();
|
|
236
|
+
this.deltaStorageDelayId = uuid();
|
|
237
|
+
this.deltaStreamDelayId = uuid();
|
|
238
|
+
this.messageBuffer = [];
|
|
239
|
+
const props = {
|
|
240
|
+
incomingOpHandler: (messages, reason) => {
|
|
241
|
+
try {
|
|
242
|
+
this.enqueueMessages(messages, reason);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.logger.sendErrorEvent({ eventName: "EnqueueMessages_Exception" }, error);
|
|
246
|
+
this.close(normalizeError(error));
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
signalHandler: (signals) => {
|
|
250
|
+
for (const signal of signals) {
|
|
251
|
+
this._inboundSignal.push(signal);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
reconnectionDelayHandler: (delayMs, error) => this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
|
|
255
|
+
closeHandler: (error) => this.close(error),
|
|
256
|
+
disconnectHandler: (reason) => this.disconnectHandler(reason),
|
|
257
|
+
connectHandler: (connection) => this.connectHandler(connection),
|
|
258
|
+
pongHandler: (latency) => this.emit("pong", latency),
|
|
259
|
+
readonlyChangeHandler: (readonly, readonlyConnectionReason) => {
|
|
260
|
+
safeRaiseEvent(this, this.logger, "readonly", readonly, readonlyConnectionReason);
|
|
261
|
+
},
|
|
262
|
+
establishConnectionHandler: (reason) => this.establishingConnection(reason),
|
|
263
|
+
cancelConnectionHandler: (reason) => this.cancelEstablishingConnection(reason),
|
|
264
|
+
};
|
|
265
|
+
this.connectionManager = createConnectionManager(props);
|
|
266
|
+
this._inbound = new DeltaQueue((op) => {
|
|
267
|
+
this.processInboundMessage(op);
|
|
268
|
+
});
|
|
269
|
+
this._inbound.on("error", (error) => {
|
|
270
|
+
this.close(DataProcessingError.wrapIfUnrecognized(error, "deltaManagerInboundErrorHandler", this.lastMessage));
|
|
271
|
+
});
|
|
272
|
+
// Inbound signal queue
|
|
273
|
+
this._inboundSignal = new DeltaQueue((message) => {
|
|
274
|
+
if (this.handler === undefined) {
|
|
275
|
+
throw new Error("Attempted to process an inbound signal without a handler attached");
|
|
276
|
+
}
|
|
277
|
+
this.handler.processSignal({
|
|
278
|
+
clientId: message.clientId,
|
|
279
|
+
content: JSON.parse(message.content),
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
this._inboundSignal.on("error", (error) => {
|
|
283
|
+
this.close(normalizeError(error));
|
|
284
|
+
});
|
|
285
|
+
// Initially, all queues are created paused.
|
|
286
|
+
// - outbound is flipped back and forth in setupNewSuccessfulConnection / disconnectFromDeltaStream
|
|
287
|
+
// - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
|
|
288
|
+
}
|
|
289
|
+
cancelEstablishingConnection(reason) {
|
|
290
|
+
this.emit("cancelEstablishingConnection", reason);
|
|
291
|
+
}
|
|
292
|
+
establishingConnection(reason) {
|
|
293
|
+
this.emit("establishingConnection", reason);
|
|
187
294
|
}
|
|
188
295
|
connectHandler(connection) {
|
|
189
296
|
this.refreshDelayInfo(this.deltaStreamDelayId);
|
|
@@ -202,8 +309,10 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
202
309
|
// state. As requirements change, so should these checks.
|
|
203
310
|
assert(this.messageBuffer.length === 0, 0x0e9 /* "messageBuffer is not empty on new connection" */);
|
|
204
311
|
this.opsSize = 0;
|
|
205
|
-
this.
|
|
206
|
-
|
|
312
|
+
this.noOpCount = 0;
|
|
313
|
+
this.emit("connect", connection, checkpointSequenceNumber !== undefined
|
|
314
|
+
? this.lastObservedSeqNumber - this.lastSequenceNumber
|
|
315
|
+
: undefined);
|
|
207
316
|
// If we got some initial ops, then we know the gap and call above fetched ops to fill it.
|
|
208
317
|
// Same is true for "write" mode even if we have no ops - we will get "join" own op very very soon.
|
|
209
318
|
// However if we are connecting as view-only, then there is no good signal to realize if client is behind.
|
|
@@ -219,16 +328,12 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
219
328
|
this.fetchMissingDeltas("AfterReadConnection");
|
|
220
329
|
}
|
|
221
330
|
}
|
|
222
|
-
dispose() {
|
|
223
|
-
throw new Error("Not implemented.");
|
|
224
|
-
}
|
|
225
331
|
/**
|
|
226
332
|
* Sets the sequence number from which inbound messages should be returned
|
|
227
333
|
*/
|
|
228
|
-
async attachOpHandler(minSequenceNumber, sequenceNumber,
|
|
334
|
+
async attachOpHandler(minSequenceNumber, sequenceNumber, handler, prefetchType = "none") {
|
|
229
335
|
this.initSequenceNumber = sequenceNumber;
|
|
230
336
|
this.lastProcessedSequenceNumber = sequenceNumber;
|
|
231
|
-
this.baseTerm = term;
|
|
232
337
|
this.minSequenceNumber = minSequenceNumber;
|
|
233
338
|
this.lastQueuedSequenceNumber = sequenceNumber;
|
|
234
339
|
this.lastObservedSeqNumber = sequenceNumber;
|
|
@@ -243,7 +348,7 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
243
348
|
// setupNewSuccessfulConnection. But it should do nothing, because there is no way to fetch ops before
|
|
244
349
|
// we know snapshot sequence number that is set in attachOpHandler. So all such calls should be noop.
|
|
245
350
|
assert(this.fetchReason === undefined, 0x268 /* "There can't be pending fetch that early in boot sequence!" */);
|
|
246
|
-
if (this.
|
|
351
|
+
if (this._closed) {
|
|
247
352
|
return;
|
|
248
353
|
}
|
|
249
354
|
this._inbound.resume();
|
|
@@ -264,8 +369,7 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
264
369
|
assert(this.fetchReason !== undefined || this.pending.length === 0, 0x269 /* "pending ops are not dropped" */);
|
|
265
370
|
}
|
|
266
371
|
connect(args) {
|
|
267
|
-
|
|
268
|
-
const fetchOpsFromStorage = (_a = args.fetchOpsFromStorage) !== null && _a !== void 0 ? _a : true;
|
|
372
|
+
const fetchOpsFromStorage = args.fetchOpsFromStorage ?? true;
|
|
269
373
|
logIfFalse(this.handler !== undefined || !fetchOpsFromStorage, this.logger, "CantFetchWithoutBaseline"); // can't fetch if no baseline
|
|
270
374
|
// Note: There is race condition here.
|
|
271
375
|
// We want to issue request to storage as soon as possible, to
|
|
@@ -278,9 +382,9 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
278
382
|
// on the wire, we might be always behind.
|
|
279
383
|
// See comment at the end of "connect" handler
|
|
280
384
|
if (fetchOpsFromStorage) {
|
|
281
|
-
this.fetchMissingDeltas(args.reason);
|
|
385
|
+
this.fetchMissingDeltas(args.reason.text);
|
|
282
386
|
}
|
|
283
|
-
this.connectionManager.connect(args.mode);
|
|
387
|
+
this.connectionManager.connect(args.reason, args.mode);
|
|
284
388
|
}
|
|
285
389
|
async getDeltas(from, // inclusive
|
|
286
390
|
to, // exclusive
|
|
@@ -298,8 +402,14 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
298
402
|
// It is possible that due to asynchrony (including await above), required ops were already
|
|
299
403
|
// received through delta stream. Validate that before moving forward.
|
|
300
404
|
if (this.lastQueuedSequenceNumber >= lastExpectedOp) {
|
|
301
|
-
this.logger.sendPerformanceEvent(
|
|
302
|
-
|
|
405
|
+
this.logger.sendPerformanceEvent({
|
|
406
|
+
reason: fetchReason,
|
|
407
|
+
eventName: "ExtraStorageCall",
|
|
408
|
+
early: true,
|
|
409
|
+
from,
|
|
410
|
+
to,
|
|
411
|
+
...this.connectionManager.connectionVerboseProps,
|
|
412
|
+
});
|
|
303
413
|
return;
|
|
304
414
|
}
|
|
305
415
|
// Be prepared for the case where webSocket would receive the ops that we are trying to fill through
|
|
@@ -324,14 +434,14 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
324
434
|
// This is useless for known ranges (to is defined) as it means request is over either way.
|
|
325
435
|
// And it will cancel unbound request too early, not allowing us to learn where the end of the file is.
|
|
326
436
|
if (!opsFromFetch && cancelFetch(op)) {
|
|
327
|
-
controller.abort();
|
|
437
|
+
controller.abort("DeltaManager getDeltas fetch cancelled");
|
|
328
438
|
this._inbound.off("push", opListener);
|
|
329
439
|
}
|
|
330
440
|
};
|
|
331
441
|
try {
|
|
332
442
|
this._inbound.on("push", opListener);
|
|
333
443
|
assert(this.closeAbortController.signal.onabort === null, 0x1e8 /* "reentrancy" */);
|
|
334
|
-
this.closeAbortController.signal.onabort = () => controller.abort();
|
|
444
|
+
this.closeAbortController.signal.onabort = () => controller.abort(this.closeAbortController.signal.reason);
|
|
335
445
|
const stream = this.deltaStorage.fetchMessages(from, // inclusive
|
|
336
446
|
to, // exclusive
|
|
337
447
|
controller.signal, cacheOnly, fetchReason);
|
|
@@ -351,6 +461,13 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
351
461
|
}
|
|
352
462
|
}
|
|
353
463
|
finally {
|
|
464
|
+
if (controller.signal.aborted) {
|
|
465
|
+
this.logger.sendTelemetryEvent({
|
|
466
|
+
eventName: "DeltaManager_GetDeltasAborted",
|
|
467
|
+
fetchReason,
|
|
468
|
+
reason: controller.signal.reason,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
354
471
|
this.closeAbortController.signal.onabort = null;
|
|
355
472
|
this._inbound.off("push", opListener);
|
|
356
473
|
assert(!opsFromFetch, 0x289 /* "logic error" */);
|
|
@@ -358,14 +475,47 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
358
475
|
}
|
|
359
476
|
/**
|
|
360
477
|
* Closes the connection and clears inbound & outbound queues.
|
|
478
|
+
*
|
|
479
|
+
* Differences from dispose:
|
|
480
|
+
* - close will trigger readonly notification
|
|
481
|
+
* - close emits "closed"
|
|
482
|
+
* - close cannot be called after dispose
|
|
361
483
|
*/
|
|
362
484
|
close(error) {
|
|
363
|
-
if (this.
|
|
485
|
+
if (this._closed) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this._closed = true;
|
|
489
|
+
this.connectionManager.dispose(error, true /* switchToReadonly */);
|
|
490
|
+
this.clearQueues();
|
|
491
|
+
this.emit("closed", error);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Disposes the connection and clears the inbound & outbound queues.
|
|
495
|
+
*
|
|
496
|
+
* Differences from close:
|
|
497
|
+
* - dispose will emit "disposed"
|
|
498
|
+
* - dispose will remove all listeners
|
|
499
|
+
* - dispose can be called after closure
|
|
500
|
+
*/
|
|
501
|
+
dispose(error) {
|
|
502
|
+
if (this._disposed) {
|
|
364
503
|
return;
|
|
365
504
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
505
|
+
if (error !== undefined && !isFluidError(error)) {
|
|
506
|
+
throw new UsageError("Error must be a Fluid error");
|
|
507
|
+
}
|
|
508
|
+
this._disposed = true;
|
|
509
|
+
this._closed = true; // We consider "disposed" as a further state than "closed"
|
|
510
|
+
this.connectionManager.dispose(error, false /* switchToReadonly */);
|
|
511
|
+
this.clearQueues();
|
|
512
|
+
// This needs to be the last thing we do (before removing listeners), as it causes
|
|
513
|
+
// Container to dispose context and break ability of data stores / runtime to "hear" from delta manager.
|
|
514
|
+
this.emit("disposed", error);
|
|
515
|
+
this.removeAllListeners();
|
|
516
|
+
}
|
|
517
|
+
clearQueues() {
|
|
518
|
+
this.closeAbortController.abort("DeltaManager is closed");
|
|
369
519
|
this._inbound.clear();
|
|
370
520
|
this._inboundSignal.clear();
|
|
371
521
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
@@ -374,11 +524,6 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
374
524
|
this._inboundSignal.pause();
|
|
375
525
|
// Drop pending messages - this will ensure catchUp() does not go into infinite loop
|
|
376
526
|
this.pending = [];
|
|
377
|
-
// This needs to be the last thing we do (before removing listeners), as it causes
|
|
378
|
-
// Container to dispose context and break ability of data stores / runtime to "hear"
|
|
379
|
-
// from delta manager, including notification (above) about readonly state.
|
|
380
|
-
this.emit("closed", error);
|
|
381
|
-
this.removeAllListeners();
|
|
382
527
|
}
|
|
383
528
|
refreshDelayInfo(id) {
|
|
384
529
|
this.throttlingIdSet.delete(id);
|
|
@@ -399,7 +544,7 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
399
544
|
emitDelayInfo(id, delayMs, error) {
|
|
400
545
|
const timeNow = Date.now();
|
|
401
546
|
this.throttlingIdSet.add(id);
|
|
402
|
-
if (delayMs > 0 &&
|
|
547
|
+
if (delayMs > 0 && timeNow + delayMs > this.timeTillThrottling) {
|
|
403
548
|
this.timeTillThrottling = timeNow + delayMs;
|
|
404
549
|
const throttlingWarning = ThrottlingWarning.wrap(error, delayMs / 1000 /* retryAfterSeconds */, this.logger);
|
|
405
550
|
this.emit("throttled", throttlingWarning);
|
|
@@ -410,13 +555,12 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
410
555
|
// for example, it's not clear if serverMetadata or timestamp property is a property of message or server state.
|
|
411
556
|
// We only extract the most obvious fields that are sufficient (with high probability) to detect sequence number
|
|
412
557
|
// reuse.
|
|
413
|
-
// Also payload goes to telemetry, so no
|
|
558
|
+
// Also payload goes to telemetry, so no content or anything else that shouldn't be logged for privacy reasons
|
|
414
559
|
// Note: It's possible for a duplicate op to be broadcasted and have everything the same except the timestamp.
|
|
415
560
|
comparableMessagePayload(m) {
|
|
416
561
|
return `${m.clientId}-${m.type}-${m.minimumSequenceNumber}-${m.referenceSequenceNumber}-${m.timestamp}`;
|
|
417
562
|
}
|
|
418
563
|
enqueueMessages(messages, reason, allowGaps = false) {
|
|
419
|
-
var _a, _b;
|
|
420
564
|
if (this.handler === undefined) {
|
|
421
565
|
// We did not setup handler yet.
|
|
422
566
|
// This happens when we connect to web socket faster than we get attributes for container
|
|
@@ -463,31 +607,48 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
463
607
|
}
|
|
464
608
|
let eventName;
|
|
465
609
|
// Report if we found some issues
|
|
466
|
-
if (duplicate !== 0 ||
|
|
610
|
+
if (duplicate !== 0 ||
|
|
611
|
+
(gap !== 0 && !allowGaps) ||
|
|
612
|
+
(initialGap > 0 && this.fetchReason === undefined)) {
|
|
467
613
|
eventName = "enqueueMessages";
|
|
468
614
|
// Also report if we are fetching ops, and same range comes in, thus making this fetch obsolete.
|
|
469
615
|
}
|
|
470
|
-
else if (this.fetchReason !== undefined &&
|
|
471
|
-
|
|
616
|
+
else if (this.fetchReason !== undefined &&
|
|
617
|
+
this.fetchReason !== reason &&
|
|
618
|
+
from <= this.lastQueuedSequenceNumber + 1 &&
|
|
619
|
+
last > this.lastQueuedSequenceNumber) {
|
|
472
620
|
eventName = "enqueueMessagesExtraFetch";
|
|
473
621
|
}
|
|
474
622
|
// Report if there is something to report
|
|
475
623
|
// Do not report when pending fetch is in progress, as such reporting will not
|
|
476
624
|
// correctly take into account pending ops.
|
|
477
625
|
if (eventName !== undefined) {
|
|
478
|
-
this.logger.sendPerformanceEvent(
|
|
479
|
-
|
|
626
|
+
this.logger.sendPerformanceEvent({
|
|
627
|
+
eventName,
|
|
628
|
+
reason,
|
|
629
|
+
previousReason: this.prevEnqueueMessagesReason,
|
|
630
|
+
from,
|
|
631
|
+
to: last + 1,
|
|
632
|
+
length: messages.length,
|
|
633
|
+
fetchReason: this.fetchReason,
|
|
634
|
+
duplicate: duplicate > 0 ? duplicate : undefined,
|
|
635
|
+
initialGap: initialGap !== 0 ? initialGap : undefined,
|
|
636
|
+
gap: gap > 0 ? gap : undefined,
|
|
637
|
+
firstMissing,
|
|
638
|
+
dmInitialSeqNumber: this.initialSequenceNumber,
|
|
639
|
+
...this.connectionManager.connectionVerboseProps,
|
|
640
|
+
});
|
|
480
641
|
}
|
|
481
642
|
}
|
|
482
643
|
this.updateLatestKnownOpSeqNumber(messages[messages.length - 1].sequenceNumber);
|
|
483
|
-
const n =
|
|
644
|
+
const n = this.previouslyProcessedMessage?.sequenceNumber;
|
|
484
645
|
assert(n === undefined || n === this.lastQueuedSequenceNumber, 0x0ec /* "Unexpected value for previously processed message's sequence number" */);
|
|
485
646
|
for (const message of messages) {
|
|
486
647
|
// Check that the messages are arriving in the expected order
|
|
487
648
|
if (message.sequenceNumber <= this.lastQueuedSequenceNumber) {
|
|
488
649
|
// Validate that we do not have data loss, i.e. sequencing is reset and started again
|
|
489
650
|
// with numbers that this client already observed before.
|
|
490
|
-
if (
|
|
651
|
+
if (this.previouslyProcessedMessage?.sequenceNumber === message.sequenceNumber) {
|
|
491
652
|
const message1 = this.comparableMessagePayload(this.previouslyProcessedMessage);
|
|
492
653
|
const message2 = this.comparableMessagePayload(message);
|
|
493
654
|
if (message1 !== message2) {
|
|
@@ -499,8 +660,8 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
499
660
|
// hit. One example is that some clients could be submitting ops to two different service
|
|
500
661
|
// instances such that the same sequence number is reused for two different ops.
|
|
501
662
|
// pre-0.58 error message: twoMessagesWithSameSeqNumAndDifferentPayload
|
|
502
|
-
"Found two messages with the same sequenceNumber but different payloads. Likely to be a "
|
|
503
|
-
|
|
663
|
+
"Found two messages with the same sequenceNumber but different payloads. Likely to be a " +
|
|
664
|
+
"service issue", DriverErrorTypes.fileOverwrittenInStorage, {
|
|
504
665
|
clientId: this.connectionManager.clientId,
|
|
505
666
|
sequenceNumber: message.sequenceNumber,
|
|
506
667
|
message1,
|
|
@@ -528,40 +689,73 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
528
689
|
}
|
|
529
690
|
processInboundMessage(message) {
|
|
530
691
|
const startTime = Date.now();
|
|
692
|
+
assert(!this.currentlyProcessingOps, 0x3af /* Already processing ops. */);
|
|
693
|
+
this.currentlyProcessingOps = true;
|
|
531
694
|
this.lastProcessedMessage = message;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
695
|
+
const isString = typeof message.clientId === "string";
|
|
696
|
+
assert(message.clientId === null || isString, 0x41a /* undefined or string */);
|
|
697
|
+
// All client messages are coming from some client, and should have clientId,
|
|
698
|
+
// and non-client message should not have clientId. But, there are two exceptions:
|
|
699
|
+
// 1. (Legacy) We can see message.type === "attach" or "chunkedOp" for legacy files before RTM
|
|
700
|
+
// 2. Non-immediate noops (contents: null) can be sent by service without clientId
|
|
701
|
+
if (!isString && isClientMessage(message) && message.type !== MessageType.NoOp) {
|
|
702
|
+
throw new DataCorruptionError("Mismatch in clientId", {
|
|
703
|
+
...extractSafePropertiesFromMessage(message),
|
|
704
|
+
messageType: message.type,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
536
707
|
// TODO Remove after SPO picks up the latest build.
|
|
537
|
-
if (typeof message.contents === "string"
|
|
538
|
-
|
|
539
|
-
|
|
708
|
+
if (typeof message.contents === "string" &&
|
|
709
|
+
message.contents !== "" &&
|
|
710
|
+
message.type !== MessageType.ClientLeave) {
|
|
540
711
|
message.contents = JSON.parse(message.contents);
|
|
541
712
|
}
|
|
713
|
+
// Validate client sequence number has no gap. Decrement the noOpCount by gap
|
|
714
|
+
// If the count ends up negative, that means we have a real gap and throw error
|
|
715
|
+
if (this.connectionManager.clientId !== undefined &&
|
|
716
|
+
this.connectionManager.clientId === message.clientId) {
|
|
717
|
+
if (message.type === MessageType.NoOp) {
|
|
718
|
+
this.noOpCount--;
|
|
719
|
+
}
|
|
720
|
+
const clientSeqNumGap = message.clientSequenceNumber - this.lastClientSequenceNumber - 1;
|
|
721
|
+
this.noOpCount -= clientSeqNumGap;
|
|
722
|
+
if (this.noOpCount < 0) {
|
|
723
|
+
throw new Error(`gap in client sequence number: ${clientSeqNumGap}`);
|
|
724
|
+
}
|
|
725
|
+
this.lastClientSequenceNumber = message.clientSequenceNumber;
|
|
726
|
+
}
|
|
542
727
|
this.connectionManager.beforeProcessingIncomingOp(message);
|
|
543
728
|
// Watch the minimum sequence number and be ready to update as needed
|
|
544
729
|
if (this.minSequenceNumber > message.minimumSequenceNumber) {
|
|
545
730
|
// pre-0.58 error message: msnMovesBackwards
|
|
546
|
-
throw new DataCorruptionError("Found a lower minimumSequenceNumber (msn) than previously recorded",
|
|
731
|
+
throw new DataCorruptionError("Found a lower minimumSequenceNumber (msn) than previously recorded", {
|
|
732
|
+
...extractSafePropertiesFromMessage(message),
|
|
733
|
+
clientId: this.connectionManager.clientId,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
// Client ops: MSN has to be lower than sequence #, as client can continue to send ops with same
|
|
737
|
+
// reference sequence number as this op.
|
|
738
|
+
// System ops (when no clients are connected) are the only ops where equation is possible.
|
|
739
|
+
const diff = message.sequenceNumber - message.minimumSequenceNumber;
|
|
740
|
+
if (diff < 0 || (diff === 0 && message.clientId !== null)) {
|
|
741
|
+
throw new DataCorruptionError("MSN has to be lower than sequence #", extractSafePropertiesFromMessage(message));
|
|
547
742
|
}
|
|
548
743
|
this.minSequenceNumber = message.minimumSequenceNumber;
|
|
549
744
|
if (message.sequenceNumber !== this.lastProcessedSequenceNumber + 1) {
|
|
550
745
|
// pre-0.58 error message: nonSequentialSequenceNumber
|
|
551
|
-
throw new DataCorruptionError("Found a non-Sequential sequenceNumber",
|
|
746
|
+
throw new DataCorruptionError("Found a non-Sequential sequenceNumber", {
|
|
747
|
+
...extractSafePropertiesFromMessage(message),
|
|
748
|
+
clientId: this.connectionManager.clientId,
|
|
749
|
+
});
|
|
552
750
|
}
|
|
553
751
|
this.lastProcessedSequenceNumber = message.sequenceNumber;
|
|
554
752
|
// a bunch of code assumes that this is true
|
|
555
753
|
assert(this.lastProcessedSequenceNumber <= this.lastObservedSeqNumber, 0x267 /* "lastObservedSeqNumber should be updated first" */);
|
|
556
|
-
// Back-compat for older server with no term
|
|
557
|
-
if (message.term === undefined) {
|
|
558
|
-
message.term = 1;
|
|
559
|
-
}
|
|
560
|
-
this.baseTerm = message.term;
|
|
561
754
|
if (this.handler === undefined) {
|
|
562
755
|
throw new Error("Attempted to process an inbound message without a handler attached");
|
|
563
756
|
}
|
|
564
757
|
this.handler.process(message);
|
|
758
|
+
this.currentlyProcessingOps = false;
|
|
565
759
|
const endTime = Date.now();
|
|
566
760
|
// Should be last, after changing this.lastProcessedSequenceNumber above, as many callers
|
|
567
761
|
// test this.lastProcessedSequenceNumber instead of using op.sequenceNumber itself.
|
|
@@ -576,16 +770,18 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
576
770
|
});
|
|
577
771
|
}
|
|
578
772
|
/**
|
|
579
|
-
|
|
580
|
-
|
|
773
|
+
* Retrieves the missing deltas between the given sequence numbers
|
|
774
|
+
*/
|
|
581
775
|
async fetchMissingDeltasCore(reason, cacheOnly, to) {
|
|
582
|
-
var _a;
|
|
583
776
|
// Exit out early if we're already fetching deltas
|
|
584
777
|
if (this.fetchReason !== undefined) {
|
|
585
778
|
return;
|
|
586
779
|
}
|
|
587
|
-
if (this.
|
|
588
|
-
this.logger.sendTelemetryEvent({
|
|
780
|
+
if (this._closed) {
|
|
781
|
+
this.logger.sendTelemetryEvent({
|
|
782
|
+
eventName: "fetchMissingDeltasClosedConnection",
|
|
783
|
+
reason,
|
|
784
|
+
});
|
|
589
785
|
return;
|
|
590
786
|
}
|
|
591
787
|
if (this.handler === undefined) {
|
|
@@ -595,7 +791,7 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
595
791
|
}
|
|
596
792
|
try {
|
|
597
793
|
let from = this.lastQueuedSequenceNumber + 1;
|
|
598
|
-
const n =
|
|
794
|
+
const n = this.previouslyProcessedMessage?.sequenceNumber;
|
|
599
795
|
if (n !== undefined) {
|
|
600
796
|
// If we already processed at least one op, then we have this.previouslyProcessedMessage populated
|
|
601
797
|
// and can use it to validate that we are operating on same file, i.e. it was not overwritten.
|
|
@@ -627,7 +823,7 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
627
823
|
* Sorts pending ops and attempts to apply them
|
|
628
824
|
*/
|
|
629
825
|
processPendingOps(reason) {
|
|
630
|
-
if (this.
|
|
826
|
+
if (this._closed) {
|
|
631
827
|
return;
|
|
632
828
|
}
|
|
633
829
|
assert(this.handler !== undefined, 0x26c /* "handler should be installed" */);
|
|
@@ -660,4 +856,4 @@ export class DeltaManager extends TypedEventEmitter {
|
|
|
660
856
|
}
|
|
661
857
|
}
|
|
662
858
|
}
|
|
663
|
-
//# sourceMappingURL=deltaManager.
|
|
859
|
+
//# sourceMappingURL=deltaManager.mjs.map
|