@fluidframework/container-loader 1.2.7 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/dist/audience.d.ts +2 -6
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +6 -11
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +29 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/catchUpMonitor.js +43 -0
- package/dist/catchUpMonitor.js.map +1 -0
- package/dist/collabWindowTracker.d.ts +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js +12 -4
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +43 -22
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts +0 -5
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js +0 -5
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +84 -22
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +172 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +29 -17
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +181 -171
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +18 -8
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +11 -25
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +51 -17
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +5 -5
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +4 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +39 -12
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +4 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaQueue.d.ts +9 -2
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +31 -26
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +8 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -3
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +27 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +79 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +2 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/lib/audience.d.ts +2 -6
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +6 -11
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +29 -0
- package/lib/catchUpMonitor.d.ts.map +1 -0
- package/lib/catchUpMonitor.js +39 -0
- package/lib/catchUpMonitor.js.map +1 -0
- package/lib/collabWindowTracker.d.ts +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js +13 -5
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +44 -25
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts +0 -5
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js +0 -5
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +84 -22
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +171 -59
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +29 -17
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +184 -174
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +19 -9
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +11 -25
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +51 -16
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +5 -5
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +4 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +41 -14
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +4 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaQueue.d.ts +9 -2
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +32 -27
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +8 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +4 -3
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +27 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +75 -0
- package/lib/protocol.js.map +1 -0
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/package.json +27 -19
- package/src/audience.ts +8 -14
- package/src/catchUpMonitor.ts +59 -0
- package/src/collabWindowTracker.ts +15 -6
- package/src/connectionManager.ts +56 -33
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +235 -70
- package/src/container.ts +241 -218
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +71 -16
- package/src/contracts.ts +7 -7
- package/src/deltaManager.ts +48 -15
- package/src/deltaQueue.ts +34 -28
- package/src/index.ts +4 -0
- package/src/loader.ts +14 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +120 -0
- package/src/retriableDocumentStorageService.ts +8 -2
package/lib/container.js
CHANGED
|
@@ -7,9 +7,8 @@ import merge from "lodash/merge";
|
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
8
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
|
-
import {
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl,
|
|
12
|
-
import { ProtocolOpHandlerWithClientValidation, } from "@fluidframework/protocol-base";
|
|
10
|
+
import { GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
13
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
14
13
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
15
14
|
import { Audience } from "./audience";
|
|
@@ -19,27 +18,31 @@ import { DeltaManager } from "./deltaManager";
|
|
|
19
18
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
20
19
|
import { RelativeLoader } from "./loader";
|
|
21
20
|
import { pkgVersion } from "./packageVersion";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
25
|
-
import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
21
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
22
|
+
import { createConnectionStateHandler, } from "./connectionStateHandler";
|
|
26
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
27
24
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
28
25
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
29
26
|
import { ConnectionManager } from "./connectionManager";
|
|
30
27
|
import { ConnectionState } from "./connectionState";
|
|
28
|
+
import { ProtocolHandler, } from "./protocol";
|
|
31
29
|
const detachedContainerRefSeqNumber = 0;
|
|
32
30
|
const dirtyContainerEvent = "dirty";
|
|
33
31
|
const savedContainerEvent = "saved";
|
|
34
32
|
/**
|
|
35
|
-
* Waits until container connects to delta storage and gets up-to-date
|
|
33
|
+
* Waits until container connects to delta storage and gets up-to-date.
|
|
34
|
+
*
|
|
36
35
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
37
36
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
37
|
+
*
|
|
38
38
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
39
39
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
*
|
|
41
|
+
* @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
|
|
42
|
+
*
|
|
43
|
+
* `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
|
|
44
|
+
* but it maybe still behind.
|
|
45
|
+
*
|
|
43
46
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
44
47
|
*/
|
|
45
48
|
export async function waitContainerToCatchUp(container) {
|
|
@@ -57,6 +60,10 @@ export async function waitContainerToCatchUp(container) {
|
|
|
57
60
|
: new GenericError(baseMessage));
|
|
58
61
|
};
|
|
59
62
|
container.on("closed", closedCallback);
|
|
63
|
+
// Depending on config, transition to "connected" state may include the guarantee
|
|
64
|
+
// that all known ops have been processed. If so, we may introduce additional wait here.
|
|
65
|
+
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
66
|
+
// which is a reasonable approximation of "caught up"
|
|
60
67
|
const waitForOps = () => {
|
|
61
68
|
assert(container.connectionState === ConnectionState.CatchingUp
|
|
62
69
|
|| container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
@@ -98,9 +105,22 @@ export async function waitContainerToCatchUp(container) {
|
|
|
98
105
|
const getCodeProposal =
|
|
99
106
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
100
107
|
(quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
|
|
108
|
+
/**
|
|
109
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (1s)
|
|
110
|
+
* @param logger - logger to use
|
|
111
|
+
* @param eventName - event name
|
|
112
|
+
* @param action - functor to call and measure
|
|
113
|
+
*/
|
|
114
|
+
async function ReportIfTooLong(logger, eventName, action) {
|
|
115
|
+
const event = PerformanceEvent.start(logger, { eventName });
|
|
116
|
+
const props = await action();
|
|
117
|
+
if (event.duration > 1000) {
|
|
118
|
+
event.end(props);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
101
121
|
const summarizerClientType = "summarizer";
|
|
102
122
|
export class Container extends EventEmitterWithErrorHandling {
|
|
103
|
-
constructor(loader, config) {
|
|
123
|
+
constructor(loader, config, protocolHandlerBuilder) {
|
|
104
124
|
var _a, _b;
|
|
105
125
|
super((name, error) => {
|
|
106
126
|
this.mc.logger.sendErrorEvent({
|
|
@@ -109,6 +129,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
109
129
|
}, error);
|
|
110
130
|
});
|
|
111
131
|
this.loader = loader;
|
|
132
|
+
this.protocolHandlerBuilder = protocolHandlerBuilder;
|
|
112
133
|
// Tells if container can reconnect on losing fist connection
|
|
113
134
|
// If false, container gets closed on loss of connection.
|
|
114
135
|
this._canReconnect = true;
|
|
@@ -122,7 +143,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
122
143
|
this.attachStarted = false;
|
|
123
144
|
this._dirtyContainer = false;
|
|
124
145
|
this.setAutoReconnectTime = performance.now();
|
|
125
|
-
this._audience = new Audience();
|
|
126
146
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
127
147
|
this._resolvedUrl = config.resolvedUrl;
|
|
128
148
|
if (config.canReconnect !== undefined) {
|
|
@@ -142,6 +162,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
142
162
|
containerAttachState: () => this._attachState,
|
|
143
163
|
containerLifecycleState: () => this._lifecycleState,
|
|
144
164
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
165
|
+
serializedContainer: config.serializedContainerState !== undefined,
|
|
145
166
|
},
|
|
146
167
|
// we need to be judicious with our logging here to avoid generating too much data
|
|
147
168
|
// all data logged here should be broadly applicable, and not specific to a
|
|
@@ -154,6 +175,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
154
175
|
containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
|
|
155
176
|
containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
|
|
156
177
|
// message information to associate errors with the specific execution state
|
|
178
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
157
179
|
dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
|
|
158
180
|
dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
|
|
159
181
|
dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
|
|
@@ -164,9 +186,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
164
186
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
165
187
|
const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
|
|
166
188
|
this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
this._deltaManager = this.createDeltaManager();
|
|
190
|
+
this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
|
|
191
|
+
this.connectionStateHandler = createConnectionStateHandler({
|
|
192
|
+
logger: this.mc.logger,
|
|
193
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
194
|
+
if (value === ConnectionState.Connected) {
|
|
195
|
+
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
196
|
+
}
|
|
197
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
198
|
+
if (this._lifecycleState === "loaded") {
|
|
199
|
+
this.propagateConnectionState(false /* initial transition */);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
170
202
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
171
203
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
172
204
|
logConnectionIssue: (eventName, details) => {
|
|
@@ -174,29 +206,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
174
206
|
// its own join op. Attempt recovery option.
|
|
175
207
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
176
208
|
},
|
|
177
|
-
|
|
178
|
-
// Fire events only if container is fully loaded and not closed
|
|
179
|
-
if (this._lifecycleState === "loaded") {
|
|
180
|
-
this.propagateConnectionState();
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
}, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
|
|
209
|
+
}, this.deltaManager, this._clientId);
|
|
184
210
|
this.on(savedContainerEvent, () => {
|
|
185
211
|
this.connectionStateHandler.containerSaved();
|
|
186
212
|
});
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (this.loader.services.detachedBlobStorage !== undefined) {
|
|
191
|
-
return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
|
|
192
|
-
}
|
|
193
|
-
this.mc.logger.sendErrorEvent({
|
|
194
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
195
|
-
});
|
|
196
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
197
|
-
}
|
|
198
|
-
return this.storageService;
|
|
199
|
-
});
|
|
213
|
+
this.storageService = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
|
|
214
|
+
? () => this.captureProtocolSummary()
|
|
215
|
+
: undefined);
|
|
200
216
|
const isDomAvailable = typeof document === "object" &&
|
|
201
217
|
document !== null &&
|
|
202
218
|
typeof document.addEventListener === "function" &&
|
|
@@ -253,13 +269,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
253
269
|
/**
|
|
254
270
|
* Load an existing container.
|
|
255
271
|
*/
|
|
256
|
-
static async load(loader, loadOptions, pendingLocalState) {
|
|
272
|
+
static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
|
|
257
273
|
const container = new Container(loader, {
|
|
258
274
|
clientDetailsOverride: loadOptions.clientDetailsOverride,
|
|
259
275
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
260
276
|
canReconnect: loadOptions.canReconnect,
|
|
261
277
|
serializedContainerState: pendingLocalState,
|
|
262
|
-
});
|
|
278
|
+
}, protocolHandlerBuilder);
|
|
263
279
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
264
280
|
var _a, _b;
|
|
265
281
|
const version = loadOptions.version;
|
|
@@ -293,8 +309,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
293
309
|
/**
|
|
294
310
|
* Create a new container in a detached state.
|
|
295
311
|
*/
|
|
296
|
-
static async createDetached(loader, codeDetails) {
|
|
297
|
-
const container = new Container(loader, {});
|
|
312
|
+
static async createDetached(loader, codeDetails, protocolHandlerBuilder) {
|
|
313
|
+
const container = new Container(loader, {}, protocolHandlerBuilder);
|
|
298
314
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
|
|
299
315
|
await container.createDetached(codeDetails);
|
|
300
316
|
return container;
|
|
@@ -304,8 +320,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
304
320
|
* Create a new container in a detached state that is initialized with a
|
|
305
321
|
* snapshot from a previous detached container.
|
|
306
322
|
*/
|
|
307
|
-
static async rehydrateDetachedFromSnapshot(loader, snapshot) {
|
|
308
|
-
const container = new Container(loader, {});
|
|
323
|
+
static async rehydrateDetachedFromSnapshot(loader, snapshot, protocolHandlerBuilder) {
|
|
324
|
+
const container = new Container(loader, {}, protocolHandlerBuilder);
|
|
309
325
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
|
|
310
326
|
const deserializedSummary = JSON.parse(snapshot);
|
|
311
327
|
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
@@ -317,7 +333,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
317
333
|
// Only transition states if currently loading
|
|
318
334
|
if (this._lifecycleState === "loading") {
|
|
319
335
|
// Propagate current connection state through the system.
|
|
320
|
-
this.propagateConnectionState();
|
|
336
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
321
337
|
this._lifecycleState = "loaded";
|
|
322
338
|
}
|
|
323
339
|
}
|
|
@@ -325,13 +341,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
325
341
|
return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
|
|
326
342
|
}
|
|
327
343
|
get storage() {
|
|
328
|
-
return this.
|
|
329
|
-
}
|
|
330
|
-
get storageService() {
|
|
331
|
-
if (this._storageService === undefined) {
|
|
332
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
333
|
-
}
|
|
334
|
-
return this._storageService;
|
|
344
|
+
return this.storageService;
|
|
335
345
|
}
|
|
336
346
|
get context() {
|
|
337
347
|
if (this._context === undefined) {
|
|
@@ -372,7 +382,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
372
382
|
return this.connectionStateHandler.connectionState;
|
|
373
383
|
}
|
|
374
384
|
get connected() {
|
|
375
|
-
return this.connectionStateHandler.
|
|
385
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
376
386
|
}
|
|
377
387
|
/**
|
|
378
388
|
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
@@ -386,7 +396,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
386
396
|
* Set once this.connected is true, otherwise undefined
|
|
387
397
|
*/
|
|
388
398
|
get clientId() {
|
|
389
|
-
return this.
|
|
399
|
+
return this._clientId;
|
|
390
400
|
}
|
|
391
401
|
/**
|
|
392
402
|
* The server provided claims of the client.
|
|
@@ -418,12 +428,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
418
428
|
* Retrieves the audience associated with the document
|
|
419
429
|
*/
|
|
420
430
|
get audience() {
|
|
421
|
-
return this.
|
|
431
|
+
return this.protocolHandler.audience;
|
|
422
432
|
}
|
|
423
433
|
/**
|
|
424
434
|
* Returns true if container is dirty.
|
|
425
435
|
* Which means data loss if container is closed at that same moment
|
|
426
|
-
* Most likely that happens when there is no network connection to
|
|
436
|
+
* Most likely that happens when there is no network connection to Relay Service
|
|
427
437
|
*/
|
|
428
438
|
get isDirty() {
|
|
429
439
|
return this._dirtyContainer;
|
|
@@ -448,7 +458,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
448
458
|
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
449
459
|
}
|
|
450
460
|
closeCore(error) {
|
|
451
|
-
var _a, _b, _c
|
|
461
|
+
var _a, _b, _c;
|
|
452
462
|
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
453
463
|
try {
|
|
454
464
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -463,11 +473,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
463
473
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
464
474
|
this.connectionStateHandler.dispose();
|
|
465
475
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
466
|
-
|
|
476
|
+
this.storageService.dispose();
|
|
467
477
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
468
478
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
469
479
|
// Driver need to ensure all caches are cleared on critical errors
|
|
470
|
-
(
|
|
480
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
471
481
|
}
|
|
472
482
|
catch (exception) {
|
|
473
483
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
@@ -489,7 +499,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
489
499
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
490
500
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
491
501
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
492
|
-
assert(this._protocolHandler.attributes.term !== undefined,
|
|
502
|
+
assert(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
|
|
493
503
|
const pendingState = {
|
|
494
504
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
495
505
|
url: this.resolvedUrl.url,
|
|
@@ -497,6 +507,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
497
507
|
term: this._protocolHandler.attributes.term,
|
|
498
508
|
clientId: this.clientId,
|
|
499
509
|
};
|
|
510
|
+
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
500
511
|
this.close();
|
|
501
512
|
return JSON.stringify(pendingState);
|
|
502
513
|
}
|
|
@@ -553,7 +564,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
553
564
|
const resolvedUrl = this.service.resolvedUrl;
|
|
554
565
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
555
566
|
this._resolvedUrl = resolvedUrl;
|
|
556
|
-
await this.
|
|
567
|
+
await this.storageService.connectToService(this.service);
|
|
557
568
|
if (hasAttachmentBlobs) {
|
|
558
569
|
// upload blobs to storage
|
|
559
570
|
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
@@ -583,8 +594,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
583
594
|
}
|
|
584
595
|
this._attachState = AttachState.Attached;
|
|
585
596
|
this.emit("attached");
|
|
586
|
-
// Propagate current connection state through the system.
|
|
587
|
-
this.propagateConnectionState();
|
|
588
597
|
if (!this.closed) {
|
|
589
598
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
590
599
|
}
|
|
@@ -723,9 +732,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
723
732
|
/**
|
|
724
733
|
* Load container.
|
|
725
734
|
*
|
|
726
|
-
* @param specifiedVersion -
|
|
727
|
-
* - undefined - fetch latest snapshot
|
|
728
|
-
* - otherwise, version sha to load snapshot
|
|
735
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
729
736
|
*/
|
|
730
737
|
async load(specifiedVersion, loadMode, pendingLocalState) {
|
|
731
738
|
if (this._resolvedUrl === undefined) {
|
|
@@ -748,11 +755,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
748
755
|
this.connectToDeltaStream(connectionArgs);
|
|
749
756
|
}
|
|
750
757
|
if (!pendingLocalState) {
|
|
751
|
-
await this.
|
|
758
|
+
await this.storageService.connectToService(this.service);
|
|
752
759
|
}
|
|
753
760
|
else {
|
|
754
761
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
755
|
-
this.
|
|
762
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
756
763
|
}
|
|
757
764
|
this._attachState = AttachState.Attached;
|
|
758
765
|
// Fetch specified snapshot.
|
|
@@ -787,21 +794,26 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
787
794
|
}
|
|
788
795
|
// ...load in the existing quorum
|
|
789
796
|
// Initialize the protocol handler
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
797
|
+
if (pendingLocalState === undefined) {
|
|
798
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
this.initializeProtocolState(attributes, {
|
|
802
|
+
members: pendingLocalState.protocol.members,
|
|
803
|
+
proposals: pendingLocalState.protocol.proposals,
|
|
804
|
+
values: pendingLocalState.protocol.values,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
793
807
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
794
808
|
await this.instantiateContext(true, // existing
|
|
795
809
|
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
796
|
-
// Internal context is fully loaded at this point
|
|
797
|
-
this.setLoaded();
|
|
798
810
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
799
811
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
800
812
|
if (!this.closed) {
|
|
801
813
|
if (opsBeforeReturnP !== undefined) {
|
|
802
814
|
this._deltaManager.inbound.resume();
|
|
803
|
-
await opsBeforeReturnP;
|
|
804
|
-
await this._deltaManager.inbound.waitTillProcessingDone();
|
|
815
|
+
await ReportIfTooLong(this.mc.logger, "WaitOps", async () => { await opsBeforeReturnP; return {}; });
|
|
816
|
+
await ReportIfTooLong(this.mc.logger, "WaitOpProcessing", async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
805
817
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
806
818
|
this._deltaManager.inbound.pause();
|
|
807
819
|
}
|
|
@@ -827,9 +839,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
827
839
|
if (this.closed) {
|
|
828
840
|
throw new Error("Container was closed while load()");
|
|
829
841
|
}
|
|
842
|
+
// Internal context is fully loaded at this point
|
|
843
|
+
this.setLoaded();
|
|
830
844
|
return {
|
|
831
845
|
sequenceNumber: attributes.sequenceNumber,
|
|
832
846
|
version: versionId,
|
|
847
|
+
dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
|
|
848
|
+
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
833
849
|
};
|
|
834
850
|
}
|
|
835
851
|
async createDetached(source) {
|
|
@@ -841,9 +857,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
841
857
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
842
858
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
843
859
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
844
|
-
this.
|
|
845
|
-
|
|
846
|
-
|
|
860
|
+
this.initializeProtocolState(attributes, {
|
|
861
|
+
members: [],
|
|
862
|
+
proposals: [],
|
|
863
|
+
values: qValues,
|
|
864
|
+
});
|
|
847
865
|
// The load context - given we seeded the quorum - will be great
|
|
848
866
|
await this.instantiateContextDetached(false);
|
|
849
867
|
this.setLoaded();
|
|
@@ -854,38 +872,22 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
854
872
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
855
873
|
}
|
|
856
874
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
857
|
-
this.
|
|
858
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
875
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
876
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
859
877
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
860
878
|
// Initialize the protocol handler
|
|
861
879
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
862
|
-
const qValues = await readAndParse(this.
|
|
880
|
+
const qValues = await readAndParse(this.storageService, baseTree.blobs.quorumValues);
|
|
863
881
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
864
|
-
this.
|
|
865
|
-
|
|
866
|
-
[],
|
|
867
|
-
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []
|
|
882
|
+
this.initializeProtocolState(attributes, {
|
|
883
|
+
members: [],
|
|
884
|
+
proposals: [],
|
|
885
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
886
|
+
});
|
|
868
887
|
await this.instantiateContextDetached(true, // existing
|
|
869
888
|
snapshotTree);
|
|
870
889
|
this.setLoaded();
|
|
871
890
|
}
|
|
872
|
-
async connectStorageService() {
|
|
873
|
-
var _a, _b;
|
|
874
|
-
if (this._storageService !== undefined) {
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
878
|
-
const storageService = await this.service.connectToStorage();
|
|
879
|
-
this._storageService =
|
|
880
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
881
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
882
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
883
|
-
this._storageService =
|
|
884
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
885
|
-
}
|
|
886
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
887
|
-
assert(((_a = storageService.policies) === null || _a === void 0 ? void 0 : _a.minBlobSize) === ((_b = this.storageService.policies) === null || _b === void 0 ? void 0 : _b.minBlobSize), 0x0e0 /* "lost minBlobSize policy" */);
|
|
888
|
-
}
|
|
889
891
|
async getDocumentAttributes(storage, tree) {
|
|
890
892
|
if (tree === undefined) {
|
|
891
893
|
return {
|
|
@@ -906,22 +908,25 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
906
908
|
return attributes;
|
|
907
909
|
}
|
|
908
910
|
async initializeProtocolStateFromSnapshot(attributes, storage, snapshot) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
911
|
+
const quorumSnapshot = {
|
|
912
|
+
members: [],
|
|
913
|
+
proposals: [],
|
|
914
|
+
values: [],
|
|
915
|
+
};
|
|
912
916
|
if (snapshot !== undefined) {
|
|
913
917
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
914
|
-
[members, proposals, values] = await Promise.all([
|
|
918
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
|
|
915
919
|
readAndParse(storage, baseTree.blobs.quorumMembers),
|
|
916
920
|
readAndParse(storage, baseTree.blobs.quorumProposals),
|
|
917
921
|
readAndParse(storage, baseTree.blobs.quorumValues),
|
|
918
922
|
]);
|
|
919
923
|
}
|
|
920
|
-
|
|
921
|
-
return protocolHandler;
|
|
924
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
922
925
|
}
|
|
923
|
-
|
|
924
|
-
|
|
926
|
+
initializeProtocolState(attributes, quorumSnapshot) {
|
|
927
|
+
var _a;
|
|
928
|
+
const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
929
|
+
const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
|
|
925
930
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
926
931
|
protocol.quorum.on("error", (error) => {
|
|
927
932
|
protocolLogger.sendErrorEvent(error);
|
|
@@ -946,7 +951,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
946
951
|
});
|
|
947
952
|
}
|
|
948
953
|
});
|
|
949
|
-
|
|
954
|
+
// we need to make sure this member get set in a synchronous context,
|
|
955
|
+
// or other things can happen after the object that will be set is created, but not yet set
|
|
956
|
+
// this was breaking this._initialClients handling
|
|
957
|
+
//
|
|
958
|
+
this._protocolHandler = protocol;
|
|
950
959
|
}
|
|
951
960
|
captureProtocolSummary() {
|
|
952
961
|
const quorumSnapshot = this.protocolHandler.snapshot();
|
|
@@ -1015,13 +1024,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1015
1024
|
deltaManager.inbound.pause();
|
|
1016
1025
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1017
1026
|
deltaManager.inboundSignal.pause();
|
|
1018
|
-
deltaManager.on("connect", (details,
|
|
1019
|
-
var _a;
|
|
1020
|
-
// Back-compat for new client and old server.
|
|
1021
|
-
this._audience.clear();
|
|
1022
|
-
for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
|
|
1023
|
-
this._audience.addMember(priorClient.clientId, priorClient.client);
|
|
1024
|
-
}
|
|
1027
|
+
deltaManager.on("connect", (details, _opsBehind) => {
|
|
1025
1028
|
this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
|
|
1026
1029
|
});
|
|
1027
1030
|
deltaManager.on("disconnect", (reason) => {
|
|
@@ -1039,6 +1042,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1039
1042
|
this.emit("warning", warn);
|
|
1040
1043
|
});
|
|
1041
1044
|
deltaManager.on("readonly", (readonly) => {
|
|
1045
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1042
1046
|
this.emit("readonly", readonly);
|
|
1043
1047
|
});
|
|
1044
1048
|
deltaManager.on("closed", (error) => {
|
|
@@ -1081,12 +1085,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1081
1085
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1082
1086
|
}
|
|
1083
1087
|
}
|
|
1084
|
-
|
|
1085
|
-
connectionInitiationReason = "InitialConnect";
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1089
|
-
}
|
|
1088
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1090
1089
|
}
|
|
1091
1090
|
this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
|
|
1092
1091
|
durationFromDisconnected,
|
|
@@ -1097,49 +1096,64 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1097
1096
|
this.firstConnection = false;
|
|
1098
1097
|
}
|
|
1099
1098
|
}
|
|
1100
|
-
propagateConnectionState() {
|
|
1099
|
+
propagateConnectionState(initialTransition) {
|
|
1101
1100
|
var _a;
|
|
1101
|
+
// When container loaded, we want to propagate initial connection state.
|
|
1102
|
+
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1103
|
+
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1104
|
+
if (!initialTransition &&
|
|
1105
|
+
this.connectionState !== ConnectionState.Connected &&
|
|
1106
|
+
this.connectionState !== ConnectionState.Disconnected) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const state = this.connectionState === ConnectionState.Connected;
|
|
1102
1110
|
const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
|
|
1103
1111
|
!this.firstConnection &&
|
|
1104
1112
|
this.connectionMode === "write";
|
|
1105
1113
|
if (logOpsOnReconnect) {
|
|
1106
1114
|
this.messageCountAfterDisconnection = 0;
|
|
1107
1115
|
}
|
|
1108
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1109
1116
|
// Both protocol and context should not be undefined if we got so far.
|
|
1110
|
-
|
|
1111
|
-
this.context.setConnectionState(state, this.clientId);
|
|
1112
|
-
}
|
|
1117
|
+
this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
|
|
1113
1118
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1114
1119
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1115
1120
|
if (logOpsOnReconnect) {
|
|
1116
1121
|
this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
|
|
1117
1122
|
}
|
|
1118
1123
|
}
|
|
1124
|
+
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1119
1125
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1120
|
-
|
|
1121
|
-
switch (outboundMessageType) {
|
|
1126
|
+
switch (type) {
|
|
1122
1127
|
case MessageType.Operation:
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
// github #6451: this is only needed for staging so the server
|
|
1127
|
-
// know when the protocol tree is included
|
|
1128
|
-
// this can be removed once all clients send
|
|
1129
|
-
// protocol tree by default
|
|
1130
|
-
const summary = contents;
|
|
1131
|
-
if (summary.details === undefined) {
|
|
1132
|
-
summary.details = {};
|
|
1133
|
-
}
|
|
1134
|
-
summary.details.includesProtocolTree =
|
|
1135
|
-
this.options.summarizeProtocolTree === true;
|
|
1136
|
-
break;
|
|
1137
|
-
}
|
|
1128
|
+
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1129
|
+
case MessageType.Summarize:
|
|
1130
|
+
return this.submitSummaryMessage(contents);
|
|
1138
1131
|
default:
|
|
1139
1132
|
this.close(new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
|
|
1140
1133
|
return -1;
|
|
1141
1134
|
}
|
|
1142
|
-
|
|
1135
|
+
}
|
|
1136
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
1137
|
+
submitBatch(batch) {
|
|
1138
|
+
let clientSequenceNumber = -1;
|
|
1139
|
+
for (const message of batch) {
|
|
1140
|
+
clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
|
|
1141
|
+
message.metadata);
|
|
1142
|
+
}
|
|
1143
|
+
this._deltaManager.flush();
|
|
1144
|
+
return clientSequenceNumber;
|
|
1145
|
+
}
|
|
1146
|
+
submitSummaryMessage(summary) {
|
|
1147
|
+
// github #6451: this is only needed for staging so the server
|
|
1148
|
+
// know when the protocol tree is included
|
|
1149
|
+
// this can be removed once all clients send
|
|
1150
|
+
// protocol tree by default
|
|
1151
|
+
if (summary.details === undefined) {
|
|
1152
|
+
summary.details = {};
|
|
1153
|
+
}
|
|
1154
|
+
summary.details.includesProtocolTree =
|
|
1155
|
+
this.options.summarizeProtocolTree === true;
|
|
1156
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1143
1157
|
}
|
|
1144
1158
|
submitMessage(type, contents, batch, metadata) {
|
|
1145
1159
|
var _a;
|
|
@@ -1154,22 +1168,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1154
1168
|
processRemoteMessage(message) {
|
|
1155
1169
|
const local = this.clientId === message.clientId;
|
|
1156
1170
|
// Allow the protocol handler to process the message
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}
|
|
1161
|
-
catch (error) {
|
|
1162
|
-
this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1163
|
-
}
|
|
1164
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1165
|
-
if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
|
|
1166
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
|
|
1167
|
-
}
|
|
1168
|
-
// Forward non system messages to the loaded runtime for processing
|
|
1169
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1170
|
-
if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
|
|
1171
|
-
this.context.process(message, local, undefined);
|
|
1172
|
-
}
|
|
1171
|
+
const result = this.protocolHandler.processMessage(message, local);
|
|
1172
|
+
// Forward messages to the loaded runtime for processing
|
|
1173
|
+
this.context.process(message, local, undefined);
|
|
1173
1174
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1174
1175
|
if (this.activeConnection()) {
|
|
1175
1176
|
if (this.collabWindowTracker === undefined) {
|
|
@@ -1178,15 +1179,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1178
1179
|
// clients.
|
|
1179
1180
|
// All existing will continue to use settings they got earlier.
|
|
1180
1181
|
assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
|
|
1181
|
-
this.collabWindowTracker = new CollabWindowTracker((type
|
|
1182
|
+
this.collabWindowTracker = new CollabWindowTracker((type) => {
|
|
1182
1183
|
assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1183
|
-
this.submitMessage(type
|
|
1184
|
+
this.submitMessage(type);
|
|
1184
1185
|
}, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
|
|
1185
1186
|
}
|
|
1186
1187
|
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
1187
1188
|
}
|
|
1188
1189
|
this.emit("op", message);
|
|
1189
|
-
return result;
|
|
1190
1190
|
}
|
|
1191
1191
|
submitSignal(message) {
|
|
1192
1192
|
this._deltaManager.submitSignal(JSON.stringify(message));
|
|
@@ -1194,15 +1194,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1194
1194
|
processSignal(message) {
|
|
1195
1195
|
// No clientId indicates a system signal message.
|
|
1196
1196
|
if (message.clientId === null) {
|
|
1197
|
-
|
|
1198
|
-
if (innerContent.type === MessageType.ClientJoin) {
|
|
1199
|
-
const newClient = innerContent.content;
|
|
1200
|
-
this._audience.addMember(newClient.clientId, newClient.client);
|
|
1201
|
-
}
|
|
1202
|
-
else if (innerContent.type === MessageType.ClientLeave) {
|
|
1203
|
-
const leftClientId = innerContent.content;
|
|
1204
|
-
this._audience.removeMember(leftClientId);
|
|
1205
|
-
}
|
|
1197
|
+
this.protocolHandler.processSignal(message);
|
|
1206
1198
|
}
|
|
1207
1199
|
else {
|
|
1208
1200
|
const local = this.clientId === message.clientId;
|
|
@@ -1241,7 +1233,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1241
1233
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1242
1234
|
// are set. Global requests will still go directly to the loader
|
|
1243
1235
|
const loader = new RelativeLoader(this, this.loader);
|
|
1244
|
-
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1236
|
+
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1245
1237
|
this.emit("contextChanged", codeDetails);
|
|
1246
1238
|
}
|
|
1247
1239
|
updateDirtyContainerState(dirty) {
|
|
@@ -1254,6 +1246,24 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1254
1246
|
logContainerError(warning) {
|
|
1255
1247
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
|
|
1256
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Set the connected state of the ContainerContext
|
|
1251
|
+
* This controls the "connected" state of the ContainerRuntime as well
|
|
1252
|
+
* @param state - Is the container currently connected?
|
|
1253
|
+
* @param readonly - Is the container in readonly mode?
|
|
1254
|
+
*/
|
|
1255
|
+
setContextConnectedState(state, readonly) {
|
|
1256
|
+
var _a;
|
|
1257
|
+
if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1258
|
+
/**
|
|
1259
|
+
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1260
|
+
* ops getting through to the DeltaManager.
|
|
1261
|
+
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1262
|
+
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1263
|
+
*/
|
|
1264
|
+
this.context.setConnectionState(state && !readonly, this.clientId);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1257
1267
|
}
|
|
1258
1268
|
Container.version = "^0.1.0";
|
|
1259
1269
|
//# sourceMappingURL=container.js.map
|