@fluidframework/container-loader 2.0.0-internal.3.0.1 → 2.0.0-internal.3.1.0
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 +2 -2
- package/README.md +45 -43
- package/api-extractor.json +2 -2
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +2 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +50 -23
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +35 -16
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +96 -46
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +6 -2
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +2 -4
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -3
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +56 -27
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +4 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +3 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +16 -8
- package/dist/loader.js.map +1 -1
- 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.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +6 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +52 -25
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +35 -16
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +100 -50
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +6 -2
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -4
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -3
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +58 -29
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +4 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +3 -3
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +16 -8
- package/lib/loader.js.map +1 -1
- 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.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +6 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +6 -4
- package/lib/utils.js.map +1 -1
- package/package.json +115 -114
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +51 -46
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1006 -943
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +544 -465
- package/src/container.ts +2056 -1895
- package/src/containerContext.ts +350 -340
- package/src/containerStorageAdapter.ts +163 -153
- package/src/contracts.ts +155 -153
- package/src/deltaManager.ts +1069 -992
- package/src/deltaManagerProxy.ts +143 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +14 -17
- package/src/loader.ts +408 -403
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +93 -87
- package/src/protocolTreeDocumentStorageService.ts +30 -33
- package/src/quorum.ts +34 -34
- package/src/retriableDocumentStorageService.ts +118 -102
- package/src/utils.ts +89 -82
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
package/.eslintrc.js
CHANGED
|
@@ -4,25 +4,22 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
],
|
|
7
|
+
extends: [require.resolve("@fluidframework/eslint-config-fluid/minimal"), "prettier"],
|
|
8
|
+
parserOptions: {
|
|
9
|
+
project: ["./tsconfig.json", "./src/test/tsconfig.json"],
|
|
10
|
+
},
|
|
11
|
+
rules: {
|
|
12
|
+
// This library is used in the browser, so we don't want dependencies on most node libraries.
|
|
13
|
+
"import/no-nodejs-modules": ["error", { allow: ["events", "url"] }],
|
|
14
|
+
},
|
|
15
|
+
overrides: [
|
|
16
|
+
{
|
|
17
|
+
// Rules only for test files
|
|
18
|
+
files: ["*.spec.ts", "src/test/**"],
|
|
19
|
+
rules: {
|
|
20
|
+
// Test files are run in node only so additional node libraries can be used.
|
|
21
|
+
"import/no-nodejs-modules": ["error", { allow: ["assert", "events", "url"] }],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
28
25
|
};
|
package/.mocharc.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
"use strict";
|
|
7
7
|
|
|
8
|
-
const getFluidTestMochaConfig = require(
|
|
8
|
+
const getFluidTestMochaConfig = require("@fluidframework/mocha-test-setup/mocharc-common");
|
|
9
9
|
|
|
10
10
|
const packageDir = __dirname;
|
|
11
11
|
const config = getFluidTestMochaConfig(packageDir);
|
package/README.md
CHANGED
|
@@ -2,29 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
**Topics covered below:**
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
- [@fluidframework/container-loader](#fluidframeworkcontainer-loader)
|
|
6
|
+
- [Fluid Loader](#fluid-loader)
|
|
7
|
+
- [Expectations from host implementers](#expectations-from-host-implementers)
|
|
8
|
+
- [Expectations from container runtime and data store implementers](#expectations-from-container-runtime-and-data-store-implementers)
|
|
9
|
+
- [Container Lifetime](#container-lifetime)
|
|
10
|
+
- [Loading](#loading)
|
|
11
|
+
- [Connectivity](#connectivity)
|
|
12
|
+
- [Closure](#closure)
|
|
13
|
+
- [`Container.close()`](#containerclose)
|
|
14
|
+
- [`Container.dispose()`](#containerdispose)
|
|
15
|
+
- [Audience](#audience)
|
|
16
|
+
- [ClientID and client identification](#clientid-and-client-identification)
|
|
17
|
+
- [Error handling](#error-handling)
|
|
18
|
+
- [Connectivity events](#connectivity-events)
|
|
19
|
+
- [Readonly states](#readonly-states)
|
|
20
|
+
- [`readonly`](#readonly)
|
|
21
|
+
- [`permissions`](#permissions)
|
|
22
|
+
- [`forced`](#forced)
|
|
23
|
+
- [`storageOnly`](#storageonly)
|
|
24
|
+
- [Dirty events](#dirty-events)
|
|
23
25
|
|
|
24
26
|
**Related topics covered elsewhere:**
|
|
25
27
|
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
+
- [Quorum and Proposals](../../../server/routerlicious/packages/protocol-base/README.md)
|
|
28
29
|
|
|
29
30
|
## Fluid Loader
|
|
30
31
|
|
|
@@ -64,7 +65,7 @@ Container is returned as result of Loader.resolve() call. Loader can cache conta
|
|
|
64
65
|
|
|
65
66
|
### Connectivity
|
|
66
67
|
|
|
67
|
-
Usually container is returned when state of container (and data stores) is rehydrated from snapshot. Unless `IRequest.headers.pause` is specified, connection to ordering service will be established at some point (asynchronously) and latest Ops would be processed, allowing local changes to flow form client to server. `Container.connectionState` indicates whether connection to ordering service is established, and
|
|
68
|
+
Usually container is returned when state of container (and data stores) is rehydrated from snapshot. Unless `IRequest.headers.pause` is specified, connection to ordering service will be established at some point (asynchronously) and latest Ops would be processed, allowing local changes to flow form client to server. `Container.connectionState` indicates whether connection to ordering service is established, and [Connectivity events](#Connectivity-events) are notifying about connectivity changes. While it's highly recommended for listeners to check initial state at the moment they register for connectivity events, new listeners are called on registration to propagate current state. That is, if a container is disconnected when both "connected" and "disconnected" listeners are installed, newly installed listeners for "disconnected" event will be called on registration.
|
|
68
69
|
|
|
69
70
|
### Closure
|
|
70
71
|
|
|
@@ -82,7 +83,7 @@ When container is closed, the following is true (in no particular order):
|
|
|
82
83
|
|
|
83
84
|
1. Container.closed property is set to true
|
|
84
85
|
2. "closed" event fires on container with optional error object (indicating reason for closure; if missing - closure was due to host closing container)
|
|
85
|
-
3. "readonly" event fires on DeltaManager & Container (and Container.readonly property is set to true)
|
|
86
|
+
3. "readonly" event fires on DeltaManager & Container (and Container.readonly property is set to true) indicating to all data stores that container is read-only, and data stores should not allow local edits, as they are not going to make it.
|
|
86
87
|
4. "disconnected" event fires, if connection was active at the moment of container closure.
|
|
87
88
|
|
|
88
89
|
`"closed"` event is available on Container for hosts. `"disposed"` event is delivered to container runtime when container is closed. But container runtime can be also disposed when new code proposal is made and new version of the code (and container runtime) is loaded in accordance with it.
|
|
@@ -104,17 +105,17 @@ When container is disposed, the following is true (in no particular order):
|
|
|
104
105
|
|
|
105
106
|
`Container.audience` exposes an object that tracks all connected clients to same container.
|
|
106
107
|
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
108
|
+
- `getMembers()` can be used to retrieve current set of users
|
|
109
|
+
- `getMember()` can be used to get IClient information about particular client (returns undefined if such client is not connected)
|
|
110
|
+
- `"addMember"` event is raised when new member joins
|
|
111
|
+
- `"removeMember"` event is raised when an earlier connected member leaves (disconnects from container)
|
|
111
112
|
|
|
112
113
|
`getMembers()` and `"addMember"` event provide _IClient_ interface that describes type of connection, permissions and user information:
|
|
113
114
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
- clientId is the key - it is unique ID for a session. Please see [ClientID and client identification](#ClientId-and-client-identification) for more details on it, as well as how to properly differentiate human vs. agent clients and difference between client ID & user ID.
|
|
116
|
+
- IClient.mode in particular describes connectivity mode of a client:
|
|
117
|
+
- "write" means client has read/write connection, can change container contents, and participates in Quorum
|
|
118
|
+
- "read" indicates client as read connection. Such clients can't modify container and do not participate in quorum. That said, "read" does not indicate client permissions, i.e. client might have read-only permissions to a file, or maybe connected temporarily as read-only, to reduce COGS on server and not "modify" container (any read-write connection generates join & leave messages that modify container and change "last edited by" property)
|
|
118
119
|
|
|
119
120
|
Please note that if this client losses connection to ordering server, then audience information is not reset at that moment. It will become stale while client is disconnected, and will refresh the moment client connects back to container. For more details, please see [Connectivity events](#Connectivity-events) section
|
|
120
121
|
|
|
@@ -142,13 +143,14 @@ Errors are of [ICriticalContainerError](../../../common/lib/container-definition
|
|
|
142
143
|
readonly errorType: string;
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
There are
|
|
146
|
+
There are 4 sources of errors:
|
|
146
147
|
|
|
147
148
|
1. [ContainerErrorType](../../../common/lib/container-definitions/src/error.ts) - errors & warnings raised at loader level
|
|
148
|
-
2. [
|
|
149
|
-
3.
|
|
149
|
+
2. [DriverErrorType](../../common/driver-definitions/src/driverError.ts) - errors that are likely to be raised from the driver level
|
|
150
|
+
3. [OdspErrorType](../../drivers/odsp-driver/src/odspError.ts) and [RouterliciousErrorType](../../drivers/routerlicious-driver/src/documentDeltaConnection.ts) - errors raised by ODSP and R11S drivers.
|
|
151
|
+
4. Runtime errors, like `"summarizingError"`, `"dataCorruptionError"`. This class of errors is not pre-determined and depends on type of container loaded.
|
|
150
152
|
|
|
151
|
-
`ICriticalContainerError.errorType` is a string, which represents a union of
|
|
153
|
+
`ICriticalContainerError.errorType` is a string, which represents a union of 4 error types described above. Hosting application may package different drivers and open different types of containers, and only hosting application may have enough information to enumerate all possible error codes in such scenarios.
|
|
152
154
|
|
|
153
155
|
Hosts must listen to `"closed"` event. If error object is present there, container was closed due to error and this information needs to be communicated to user in some way. If there is no error object, it was closed due to host application calling Container.close() (without specifying error).
|
|
154
156
|
When container is closed, it is no longer connected to ordering service. It is also in read-only state, communicating to data stores not to allow user to make changes to container.
|
|
@@ -157,18 +159,18 @@ When container is closed, it is no longer connected to ordering service. It is a
|
|
|
157
159
|
|
|
158
160
|
Container raises two events to notify hosting application about connectivity issues and connectivity status.
|
|
159
161
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
+
- `"connected"` event is raised when container is connected and is up-to-date, i.e. changes are flowing between client and server.
|
|
163
|
+
- `"disconnected"` event is raised when container lost connectivity (for any reason).
|
|
162
164
|
|
|
163
165
|
Container also exposes `Container.connectionState` property to indicate current state.
|
|
164
166
|
|
|
165
|
-
In normal circumstances, container will attempt to reconnect back to ordering service as quickly as possible. But it will scale down retries if computer is offline.
|
|
167
|
+
In normal circumstances, container will attempt to reconnect back to ordering service as quickly as possible. But it will scale down retries if computer is offline. That said, if IThrottlingWarning is raised through `"warning"` handler, then container is following storage throttling policy and will attempt to reconnect after some amount of time (`IThrottlingWarning.retryAfterSeconds`).
|
|
166
168
|
|
|
167
169
|
Container will also not attempt to reconnect on lost connection if `Container.disconnect()` was called prior to loss of connection. This can be useful if the hosting application implements "user away" type of experience to reduce cost on both client and server of maintaining connection while user is away. Calling `Container.connect()` will reenable automatic reconnections, but the host might need to allow extra time for reconnection as it likely involves token fetch and processing of a lot of Ops generated by other clients while it was not connected.
|
|
168
170
|
|
|
169
171
|
Data stores should almost never listen to these events (see more on [Readonly states](#Readonly-states)), and should use consensus DDSes if they need to synchronize activity across clients. DDSes listen for these events to know when to resubmit pending Ops.
|
|
170
172
|
|
|
171
|
-
Hosting application can use these events in order to indicate to user when user changes are not propagating through the system, and thus can be lost (on browser tab being closed). It's advised to use some delay (like 5 seconds) before showing such UI, as network connectivity might be intermittent.
|
|
173
|
+
Hosting application can use these events in order to indicate to user when user changes are not propagating through the system, and thus can be lost (on browser tab being closed). It's advised to use some delay (like 5 seconds) before showing such UI, as network connectivity might be intermittent. Also if container was offline for very long period of time due to `Container.disconnect()` being called, it might take a while to get connected and current.
|
|
172
174
|
|
|
173
175
|
Please note that hosts can implement various strategies on how to handle disconnections. Some may decide to show some UX letting user know about potential loss of data if container is closed while disconnected. Others can force container to disallow user edits while offline (see [Readonly states](#Readonly-states)).
|
|
174
176
|
|
|
@@ -187,9 +189,9 @@ It contains the following properties:
|
|
|
187
189
|
|
|
188
190
|
One of the following:
|
|
189
191
|
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
192
|
+
- `true`: Container is read-only. One or more of the additional properties listed below will be `true`.
|
|
193
|
+
- `undefined`: Runtime does not know yet if file is writable or not. Currently we get a signal here only when websocket connection is made to the server.
|
|
194
|
+
- `false`: Container.forceReadonly() was never called or last call was with false, plus it's known that user has write permissions to a file.
|
|
193
195
|
|
|
194
196
|
### `permissions`
|
|
195
197
|
|
|
@@ -203,8 +205,8 @@ There are two cases when it's `true`:
|
|
|
203
205
|
`true` if the Container is in read-only mode due to the host calling `Container.forceReadonly(true)`.
|
|
204
206
|
This can be useful in scenarios like:
|
|
205
207
|
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
+
- Loss of connectivity, in scenarios where host chooses method of preventing user edits over (or in addition to) showing disconnected UX and warning user of potential data loss on closure of container.
|
|
209
|
+
- Special view-only mode in host. For example can be used by hosts for previewing container content in-place with other host content, and leveraging full-screen / separate window experience for editing.
|
|
208
210
|
|
|
209
211
|
### `storageOnly`
|
|
210
212
|
|
package/api-extractor.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
3
|
+
"extends": "@fluidframework/build-common/api-extractor-common-report.json"
|
|
4
4
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# closeAndGetPendingLocalState
|
|
2
|
+
|
|
3
|
+
Pending local state is a mechanism that Fluid provides for mitigating data loss, particularly in offline scenarios.
|
|
4
|
+
When changes are made in a Container, it puts the Container into a "dirty" state until the changes have been confirmed by the server.
|
|
5
|
+
If the Container is closed in this state, the changes may be lost if they did not reach the server (for example, if the user is offline).
|
|
6
|
+
|
|
7
|
+
`Container.closeAndGetPendingLocalState()` solves this problem by returning pending changes when closing a Container so they may be resupplied to a new container instance and resubmitted.
|
|
8
|
+
|
|
9
|
+
Additionally, the blob returned by closeAndGetPendingLocalState() contains enough data to load a container offline.
|
|
10
|
+
This method will still return a such a blob even if the container is not dirty when called.
|
|
11
|
+
|
|
12
|
+
## Using `Container.closeAndGetPendingLocalState()`
|
|
13
|
+
|
|
14
|
+
When closeAndGetPendingLocalState() is called it will return a serialized blob containing the necessary data to load and resubmit the changes in a new container.
|
|
15
|
+
|
|
16
|
+
The stashed changes blob can be used by passing it to `Loader.resolve()` when loading a new container.
|
|
17
|
+
The container will then automatically submit the stashed changes when the Container reaches the "connected" state, if the changes were not originally successful.
|
|
18
|
+
|
|
19
|
+
**It's important that these blobs are not reused, since it can result in the same changes being submitted multiple times, possibly resulting in document corruption.**
|
|
20
|
+
Instead, closeAndGetPendingLocalState() should be called again on the new container, which will return a new blob containing all its pending changes, including any still-pending stashed changes it was loaded with.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const myContainer = await myLoader.resolve(myRequest);
|
|
24
|
+
const pendingChanges = myContainer.closeAndGetPendingLocalState();
|
|
25
|
+
const myNewContainer = await myLoader.resolve(myRequest, pendingChanges);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Note that closeAndGetPendingLocalState() cannot be called on a Container that is already closed or disposed.
|
|
29
|
+
The purpose of closeAndGetPendingLocalState() is to avoid data loss when a dirty container must be closed, not recovering from internal Container errors.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
The blob contains ops and attachment blob uploads that were still pending whe the container was closed.
|
|
34
|
+
It also contains the container's last client ID, a snapshot older than the reference sequence number of the oldest pending op, and all sequenced ops the container has processed since the snapshot.
|
|
35
|
+
|
|
36
|
+
When the blob is supplied to `Loader.resolve()`, it will return a new container.
|
|
37
|
+
This container will load from the snapshot in the blob, and "replay" the saved ops in the blob by processing them one by one.
|
|
38
|
+
applyStashedOp() will be called for each stashed op after the op whose sequence number matches the stashed op's reference sequence number is processed (i.e., when the document is in the same state as when the op was originally made).
|
|
39
|
+
|
|
40
|
+
When the container connects to the delta stream and starts processing new ops, they are matched to stashed ops by client ID, so the container will not submit stashed ops that were originally successfully submitted.
|
|
41
|
+
When it processes its own join op (i.e., reaches "connected" state), the container will have seen any previously successful stashed ops, and it is safe to resubmit any remaining stashed ops.
|
|
42
|
+
|
|
43
|
+
### applyStashedOp()
|
|
44
|
+
|
|
45
|
+
When an op is created, it is given a "reference sequence number," which is the sequence number of the last op processed by the container.
|
|
46
|
+
For various reasons, the server will only sequence ops if they are within the "collab window," limited by the "minimum sequence number" of the document.
|
|
47
|
+
For this reason, if an op is not successfully submitted while its reference sequence number is within this window, runtime will call SharedObject.reSubmit() to ask the DDS to merge it with later remote changes and resubmit the change.
|
|
48
|
+
Once the op is sequenced by the server, runtime will pass it to SharedObject.process().
|
|
49
|
+
|
|
50
|
+
The job of applyStashedOp() is to return a new DDS in a new container to the same state, so that the DDS is able to handle calls to reSubmit() or process() with the op, even though the DDS itself did not submit it.
|
|
51
|
+
Once stashed ops are passed to applyStashedOp(), they are handled by runtime the same as any other pending ops.
|
package/dist/audience.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audience.d.ts","sourceRoot":"","sources":["../src/audience.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAE/D;;GAEG;AACH,qBAAa,QAAS,SAAQ,YAAa,YAAW,cAAc;
|
|
1
|
+
{"version":3,"file":"audience.d.ts","sourceRoot":"","sources":["../src/audience.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAE/D;;GAEG;AACH,qBAAa,QAAS,SAAQ,YAAa,YAAW,cAAc;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAE/C,EAAE,CACR,KAAK,EAAE,WAAW,GAAG,cAAc,EACnC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GACnD,IAAI;IAKP;;OAEG;IACI,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAenD;;;OAGG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAW9C;;OAEG;IACI,UAAU,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAIzC;;OAEG;IACI,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;CAGvD"}
|
package/dist/audience.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audience.js","sourceRoot":"","sources":["../src/audience.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,mCAAsC;AACtC,+DAAsD;AAItD;;GAEG;AACH,MAAa,QAAS,SAAQ,qBAAY;IAA1C;;
|
|
1
|
+
{"version":3,"file":"audience.js","sourceRoot":"","sources":["../src/audience.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,mCAAsC;AACtC,+DAAsD;AAItD;;GAEG;AACH,MAAa,QAAS,SAAQ,qBAAY;IAA1C;;QACkB,YAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAwDvD,CAAC;IAlDO,EAAE,CAAC,KAAa,EAAE,QAAkC;QAC1D,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACI,SAAS,CAAC,QAAgB,EAAE,OAAgB;QAClD,mGAAmG;QACnG,+FAA+F;QAC/F,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC1C,IAAA,qBAAM,EACL,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAClD,KAAK,CAAC,wDAAwD,CAC9D,CAAC;SACF;aAAM;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;SAC1C;IACF,CAAC;IAED;;;OAGG;IACI,YAAY,CAAC,QAAgB;QACnC,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,aAAa,KAAK,SAAS,EAAE;YAChC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;SACZ;aAAM;YACN,OAAO,KAAK,CAAC;SACb;IACF,CAAC;IAED;;OAEG;IACI,UAAU;QAChB,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACI,SAAS,CAAC,QAAgB;QAChC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;CACD;AAzDD,4BAyDC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { EventEmitter } from \"events\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IAudienceOwner } from \"@fluidframework/container-definitions\";\nimport { IClient } from \"@fluidframework/protocol-definitions\";\n\n/**\n * Audience represents all clients connected to the op stream.\n */\nexport class Audience extends EventEmitter implements IAudienceOwner {\n\tprivate readonly members = new Map<string, IClient>();\n\n\tpublic on(\n\t\tevent: \"addMember\" | \"removeMember\",\n\t\tlistener: (clientId: string, client: IClient) => void,\n\t): this;\n\tpublic on(event: string, listener: (...args: any[]) => void): this {\n\t\treturn super.on(event, listener);\n\t}\n\n\t/**\n\t * Adds a new client to the audience\n\t */\n\tpublic addMember(clientId: string, details: IClient) {\n\t\t// Given that signal delivery is unreliable process, we might observe same client being added twice\n\t\t// In such case we should see exactly same payload (IClient), and should not raise event twice!\n\t\tif (this.members.has(clientId)) {\n\t\t\tconst client = this.members.get(clientId);\n\t\t\tassert(\n\t\t\t\tJSON.stringify(client) === JSON.stringify(details),\n\t\t\t\t0x4b2 /* new client has different payload from existing one */,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.members.set(clientId, details);\n\t\t\tthis.emit(\"addMember\", clientId, details);\n\t\t}\n\t}\n\n\t/**\n\t * Removes a client from the audience. Only emits an event if a client is actually removed\n\t * @returns if a client was removed from the audience\n\t */\n\tpublic removeMember(clientId: string): boolean {\n\t\tconst removedClient = this.members.get(clientId);\n\t\tif (removedClient !== undefined) {\n\t\t\tthis.members.delete(clientId);\n\t\t\tthis.emit(\"removeMember\", clientId, removedClient);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves all the members in the audience\n\t */\n\tpublic getMembers(): Map<string, IClient> {\n\t\treturn new Map(this.members);\n\t}\n\n\t/**\n\t * Retrieves a specific member of the audience\n\t */\n\tpublic getMember(clientId: string): IClient | undefined {\n\t\treturn this.members.get(clientId);\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAGtE,oCAAoC;AACpC,aAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC,mGAAmG;AACnG,oBAAY,eAAe,GAAG,WAAW,CAAC;AAE1C;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;
|
|
1
|
+
{"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAGtE,oCAAoC;AACpC,aAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC,mGAAmG;AACnG,oBAAY,eAAe,GAAG,WAAW,CAAC;AAE1C;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAepD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAf1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAKxB;IAEF;;OAEG;gBAEe,YAAY,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EACrC,QAAQ,EAAE,gBAAgB;IAerC,QAAQ,EAAE,OAAO,CAAS;IAC1B,OAAO;CAQd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,+DAAsD;AAUtD;;;GAGG;AACH,MAAa,cAAc;
|
|
1
|
+
{"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,+DAAsD;AAUtD;;;GAGG;AACH,MAAa,cAAc;IAW1B;;OAEG;IACH,YACkB,YAAqC,EACrC,QAA0B;QAD1B,iBAAY,GAAZ,YAAY,CAAyB;QACrC,aAAQ,GAAR,QAAQ,CAAkB;QAdpC,aAAQ,GAAY,KAAK,CAAC;QAEjB,cAAS,GAAG,CAAC,OAA0D,EAAE,EAAE;YAC3F,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE;gBACrE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;aAChB;QACF,CAAC,CAAC;QAsBK,aAAQ,GAAY,KAAK,CAAC;QAbhC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAE5D,IAAA,qBAAM,EACL,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAC5D,KAAK,CAAC,oEAAoE,CAC1E,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3C,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC1E,CAAC;IAGM,OAAO;QACb,IAAI,IAAI,CAAC,QAAQ,EAAE;YAClB,OAAO;SACP;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;CACD;AAxCD,wCAwCC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IDeltaManager } from \"@fluidframework/container-definitions\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\n\n/** @see CatchUpMonitor for usage */\ntype CaughtUpListener = () => void;\n\n/** Monitor that emits an event when a Container has caught up to a given point in the op stream */\nexport type ICatchUpMonitor = IDisposable;\n\n/**\n * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed\n * that were known at the time the monitor was created.\n */\nexport class CatchUpMonitor implements ICatchUpMonitor {\n\tprivate readonly targetSeqNumber: number;\n\tprivate caughtUp: boolean = false;\n\n\tprivate readonly opHandler = (message: Pick<ISequencedDocumentMessage, \"sequenceNumber\">) => {\n\t\tif (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {\n\t\t\tthis.caughtUp = true;\n\t\t\tthis.listener();\n\t\t}\n\t};\n\n\t/**\n\t * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.\n\t */\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<any, any>,\n\t\tprivate readonly listener: CaughtUpListener,\n\t) {\n\t\tthis.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;\n\n\t\tassert(\n\t\t\tthis.targetSeqNumber >= this.deltaManager.lastSequenceNumber,\n\t\t\t0x37c /* Cannot wait for seqNumber below last processed sequence number */,\n\t\t);\n\n\t\tthis.deltaManager.on(\"op\", this.opHandler);\n\n\t\t// Simulate the last processed op to set caughtUp in case we already are\n\t\tthis.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });\n\t}\n\n\tpublic disposed: boolean = false;\n\tpublic dispose() {\n\t\tif (this.disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.disposed = true;\n\n\t\tthis.deltaManager.off(\"op\", this.opHandler);\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collabWindowTracker.js","sourceRoot":"","sources":["../src/collabWindowTracker.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAA6D;AAC7D,+EAA8F;AAC9F,+DAA8E;AAE9E,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC,6FAA6F;AAC7F,4GAA4G;AAC5G,yGAAyG;AACzG,2CAA2C;AAC3C,oHAAoH;AACpH,2FAA2F;AAC3F,kHAAkH;AAClH,+CAA+C;AAC/C,gHAAgH;AAChH,yFAAyF;AACzF,qHAAqH;AACrH,oDAAoD;AACpD,EAAE;AACF,kDAAkD;AAClD,oGAAoG;AACpG,iHAAiH;AACjH,sEAAsE;AACtE,4GAA4G;AAC5G,qGAAqG;AACrG,MAAa,mBAAmB;
|
|
1
|
+
{"version":3,"file":"collabWindowTracker.js","sourceRoot":"","sources":["../src/collabWindowTracker.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert, Timer } from \"@fluidframework/common-utils\";\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport { isRuntimeMessage, MessageType2 } from \"@fluidframework/driver-utils\";\n\nconst defaultNoopTimeFrequency = 2000;\nconst defaultNoopCountFrequency = 50;\n\n// Here are key considerations when deciding conditions for when to send non-immediate noops:\n// 1. Sending them too often results in increase in file size and bandwidth, as well as catch up performance\n// 2. Sending too infrequently ensures that collab window is large, and as result Sequence DDS would have\n// large catchUp blobs - see Issue #6364\n// 3. Similarly, processes that rely on \"core\" snapshot (and can't parse trailing ops, including above), like search\n// parser in SPO, will result in non-accurate results due to presence of catch up blobs.\n// 4. Ordering service used 250ms timeout to coalesce non-immediate noops. It was changed to 2000 ms to allow more\n// aggressive noop sending from client side.\n// 5. Number of ops sent by all clients is proportional to number of \"write\" clients (every client sends noops),\n// but number of sequenced noops is a function of time (one op per 2 seconds at most).\n// We should consider impact to both outbound traffic (might be huge, depends on number of clients) and file size.\n// Please also see Issue #5629 for more discussions.\n//\n// With that, the current algorithm is as follows:\n// 1. Sent noop 2000 ms of receiving an op if no ops were sent by this client within this timeframe.\n// This will ensure that MSN moves forward with reasonable speed. If that results in too many sequenced noops,\n// server timeout of 2000ms should be reconsidered to be increased.\n// 2. If there are more than 50 ops received without sending any ops, send noop to keep collab window small.\n// Note that system ops (including noops themselves) are excluded, so it's 1 noop per 50 real ops.\nexport class CollabWindowTracker {\n\tprivate opsCountSinceNoop = 0;\n\tprivate readonly timer: Timer | undefined;\n\n\tconstructor(\n\t\tprivate readonly submit: (type: MessageType) => void,\n\t\tNoopTimeFrequency: number = defaultNoopTimeFrequency,\n\t\tprivate readonly NoopCountFrequency: number = defaultNoopCountFrequency,\n\t) {\n\t\tif (NoopTimeFrequency !== Infinity) {\n\t\t\tthis.timer = new Timer(NoopTimeFrequency, () => {\n\t\t\t\t// Can get here due to this.stopSequenceNumberUpdate() not resetting timer.\n\t\t\t\t// Also timer callback can fire even after timer cancellation if it was queued before cancellation.\n\t\t\t\tif (this.opsCountSinceNoop !== 0) {\n\t\t\t\t\tthis.submitNoop(false /* immediate */);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Schedules as ack to the server to update the reference sequence number\n\t */\n\tpublic scheduleSequenceNumberUpdate(\n\t\tmessage: ISequencedDocumentMessage,\n\t\timmediateNoOp: boolean,\n\t): void {\n\t\t// While processing a message, an immediate no-op can be requested.\n\t\t// i.e. to expedite approve or commit phase of quorum.\n\t\tif (immediateNoOp) {\n\t\t\tthis.submitNoop(true /* immediate */);\n\t\t\treturn;\n\t\t}\n\n\t\t// We don't acknowledge no-ops to avoid acknowledgement cycles (i.e. ack the MSN\n\t\t// update, which updates the MSN, then ack the update, etc...).\n\t\t// Intent here is for runtime (and DDSes) not to keep too much tracking state / memory\n\t\t// due to runtime ops from other clients.\n\t\tif (!isRuntimeMessage(message)) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.opsCountSinceNoop++;\n\t\tif (this.opsCountSinceNoop === this.NoopCountFrequency) {\n\t\t\t// Ensure we only send noop after a batch of many ops is processed\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\tPromise.resolve().then(() => {\n\t\t\t\tif (this.opsCountSinceNoop >= this.NoopCountFrequency) {\n\t\t\t\t\tthis.submitNoop(false /* immediate */);\n\t\t\t\t\t// reset count now that all ops are processed\n\t\t\t\t\tthis.opsCountSinceNoop = 0;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t});\n\t\t}\n\n\t\tif (this.timer !== undefined) {\n\t\t\tif (this.opsCountSinceNoop === 1) {\n\t\t\t\tthis.timer.restart();\n\t\t\t}\n\n\t\t\tassert(this.timer.hasTimer, 0x242 /* \"has timer\" */);\n\t\t}\n\t}\n\n\tprivate submitNoop(immediate: boolean) {\n\t\t// Anything other than null is immediate noop\n\t\t// ADO:1385: Remove cast and use MessageType once definition changes propagate\n\t\tthis.submit(immediate ? (MessageType2.Accept as unknown as MessageType) : MessageType.NoOp);\n\t\tassert(\n\t\t\tthis.opsCountSinceNoop === 0,\n\t\t\t0x243 /* \"stopSequenceNumberUpdate should be called as result of sending any op!\" */,\n\t\t);\n\t}\n\n\tpublic stopSequenceNumberUpdate(): void {\n\t\tthis.opsCountSinceNoop = 0;\n\t\t// Ideally, we cancel timer here. But that will result in too often set/reset cycle if this client\n\t\t// keeps sending ops. In most cases it's actually better to let it expire (at most - 4 times per second)\n\t\t// for nothing, then have a ton of set/reset cycles.\n\t\t// Note that Timer.restart() is smart and will not change timer expiration if we keep extending timer\n\t\t// expiration - it will restart the timer instead when it fires with adjusted expiration.\n\t\t// this.timer.clear();\n\t}\n}\n"]}
|
|
@@ -68,7 +68,7 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
68
68
|
/**
|
|
69
69
|
* Returns set of props that can be logged in telemetry that provide some insights / statistics
|
|
70
70
|
* about current or last connection (if there is no connection at the moment)
|
|
71
|
-
|
|
71
|
+
*/
|
|
72
72
|
get connectionProps(): ITelemetryProperties;
|
|
73
73
|
shouldJoinWrite(): boolean;
|
|
74
74
|
/**
|
|
@@ -88,7 +88,7 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
88
88
|
/**
|
|
89
89
|
* Enables or disables automatic reconnecting.
|
|
90
90
|
* Will throw an error if reconnectMode set to Never.
|
|
91
|
-
|
|
91
|
+
*/
|
|
92
92
|
setAutoReconnect(mode: ReconnectMode): void;
|
|
93
93
|
/**
|
|
94
94
|
* Sends signal to runtime (and data stores) to be read-only.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"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"}
|