@fluidframework/container-loader 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.3.1.0.125672
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 +58 -40
- package/api-extractor.json +2 -2
- package/dist/audience.d.ts +0 -1
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +6 -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 +5 -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 +66 -32
- 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 +3 -3
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +46 -24
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +27 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +191 -57
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -2
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +11 -6
- 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 +9 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +18 -6
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +110 -37
- 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 +0 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +6 -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 +5 -4
- 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 +68 -34
- 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 +3 -3
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +46 -24
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +27 -0
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +194 -61
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +3 -2
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +11 -6
- 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 +9 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +18 -6
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +112 -39
- 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 +22 -19
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +52 -42
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1009 -938
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +544 -462
- package/src/container.ts +2040 -1785
- package/src/containerContext.ts +352 -337
- package/src/containerStorageAdapter.ts +163 -153
- package/src/contracts.ts +155 -145
- package/src/deltaManager.ts +1069 -945
- package/src/deltaManagerProxy.ts +143 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +14 -17
- package/src/loader.ts +427 -422
- 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/lib/container.js
CHANGED
|
@@ -7,7 +7,7 @@ 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 { GenericError, UsageError
|
|
10
|
+
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
11
11
|
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
13
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
@@ -19,13 +19,13 @@ import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
|
19
19
|
import { RelativeLoader } from "./loader";
|
|
20
20
|
import { pkgVersion } from "./packageVersion";
|
|
21
21
|
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
22
|
-
import { createConnectionStateHandler
|
|
22
|
+
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
23
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
24
|
-
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
24
|
+
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
|
|
25
25
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
26
26
|
import { ConnectionManager } from "./connectionManager";
|
|
27
27
|
import { ConnectionState } from "./connectionState";
|
|
28
|
-
import { ProtocolHandler
|
|
28
|
+
import { ProtocolHandler } from "./protocol";
|
|
29
29
|
const detachedContainerRefSeqNumber = 0;
|
|
30
30
|
const dirtyContainerEvent = "dirty";
|
|
31
31
|
const savedContainerEvent = "saved";
|
|
@@ -65,8 +65,8 @@ export async function waitContainerToCatchUp(container) {
|
|
|
65
65
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
66
66
|
// which is a reasonable approximation of "caught up"
|
|
67
67
|
const waitForOps = () => {
|
|
68
|
-
assert(container.connectionState === ConnectionState.CatchingUp
|
|
69
|
-
|
|
68
|
+
assert(container.connectionState === ConnectionState.CatchingUp ||
|
|
69
|
+
container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
70
70
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
71
71
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
72
72
|
assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -106,15 +106,15 @@ const getCodeProposal =
|
|
|
106
106
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
107
107
|
(quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
|
|
108
108
|
/**
|
|
109
|
-
* Helper function to report to telemetry cases where operation takes longer than expected (
|
|
109
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
110
110
|
* @param logger - logger to use
|
|
111
111
|
* @param eventName - event name
|
|
112
112
|
* @param action - functor to call and measure
|
|
113
113
|
*/
|
|
114
|
-
async function ReportIfTooLong(logger, eventName, action) {
|
|
114
|
+
export async function ReportIfTooLong(logger, eventName, action) {
|
|
115
115
|
const event = PerformanceEvent.start(logger, { eventName });
|
|
116
116
|
const props = await action();
|
|
117
|
-
if (event.duration >
|
|
117
|
+
if (event.duration > 200) {
|
|
118
118
|
event.end(props);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -133,6 +133,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
133
133
|
// Tells if container can reconnect on losing fist connection
|
|
134
134
|
// If false, container gets closed on loss of connection.
|
|
135
135
|
this._canReconnect = true;
|
|
136
|
+
/**
|
|
137
|
+
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
138
|
+
*
|
|
139
|
+
* States are allowed to progress to further states:
|
|
140
|
+
* "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
|
|
141
|
+
*
|
|
142
|
+
* For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
|
|
143
|
+
*
|
|
144
|
+
* loading: Container has been created, but is not yet in normal/loaded state
|
|
145
|
+
* loaded: Container is in normal/loaded state
|
|
146
|
+
* closing: Container has started closing process (for re-entrancy prevention)
|
|
147
|
+
* disposing: Container has started disposing process (for re-entrancy prevention)
|
|
148
|
+
* closed: Container has closed
|
|
149
|
+
* disposed: Container has been disposed
|
|
150
|
+
*/
|
|
136
151
|
this._lifecycleState = "loading";
|
|
137
152
|
this._attachState = AttachState.Detached;
|
|
138
153
|
/** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
|
|
@@ -143,6 +158,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
143
158
|
this.attachStarted = false;
|
|
144
159
|
this._dirtyContainer = false;
|
|
145
160
|
this.setAutoReconnectTime = performance.now();
|
|
161
|
+
this._disposed = false;
|
|
146
162
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
147
163
|
this._resolvedUrl = config.resolvedUrl;
|
|
148
164
|
if (config.canReconnect !== undefined) {
|
|
@@ -196,17 +212,23 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
196
212
|
}
|
|
197
213
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
198
214
|
if (this._lifecycleState === "loaded") {
|
|
199
|
-
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
215
|
+
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
216
|
+
? reason
|
|
217
|
+
: undefined /* disconnectedReason */);
|
|
200
218
|
}
|
|
201
219
|
},
|
|
202
220
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
203
221
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
204
|
-
logConnectionIssue: (eventName, details) => {
|
|
222
|
+
logConnectionIssue: (eventName, category, details) => {
|
|
205
223
|
const mode = this.connectionMode;
|
|
206
224
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
207
|
-
// its own join op.
|
|
225
|
+
// its own join op.
|
|
226
|
+
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
227
|
+
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
228
|
+
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
208
229
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName,
|
|
209
|
-
mode,
|
|
230
|
+
mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: performance.now() -
|
|
231
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
210
232
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
211
233
|
// to very slow op fetches and we will eventually get there.
|
|
212
234
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -240,7 +262,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
240
262
|
}
|
|
241
263
|
else {
|
|
242
264
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
243
|
-
setTimeout(() => {
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
this.lastVisible = undefined;
|
|
267
|
+
}, 0);
|
|
244
268
|
}
|
|
245
269
|
};
|
|
246
270
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
@@ -251,7 +275,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
251
275
|
// if we are in connecting stage.
|
|
252
276
|
this.on("newListener", (event, listener) => {
|
|
253
277
|
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
254
|
-
Promise.resolve()
|
|
278
|
+
Promise.resolve()
|
|
279
|
+
.then(() => {
|
|
255
280
|
switch (event) {
|
|
256
281
|
case dirtyContainerEvent:
|
|
257
282
|
if (this._dirtyContainer) {
|
|
@@ -275,7 +300,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
275
300
|
break;
|
|
276
301
|
default:
|
|
277
302
|
}
|
|
278
|
-
})
|
|
303
|
+
})
|
|
304
|
+
.catch((error) => {
|
|
279
305
|
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
280
306
|
});
|
|
281
307
|
});
|
|
@@ -303,7 +329,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
303
329
|
reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
|
|
304
330
|
};
|
|
305
331
|
container.on("closed", onClosed);
|
|
306
|
-
container
|
|
332
|
+
container
|
|
333
|
+
.load(version, mode, pendingLocalState)
|
|
307
334
|
.finally(() => {
|
|
308
335
|
container.removeListener("closed", onClosed);
|
|
309
336
|
})
|
|
@@ -352,7 +379,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
352
379
|
}
|
|
353
380
|
}
|
|
354
381
|
get closed() {
|
|
355
|
-
return (this._lifecycleState === "closing" ||
|
|
382
|
+
return (this._lifecycleState === "closing" ||
|
|
383
|
+
this._lifecycleState === "closed" ||
|
|
384
|
+
this._lifecycleState === "disposing" ||
|
|
385
|
+
this._lifecycleState === "disposed");
|
|
356
386
|
}
|
|
357
387
|
get storage() {
|
|
358
388
|
return this.storageService;
|
|
@@ -369,8 +399,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
369
399
|
}
|
|
370
400
|
return this._protocolHandler;
|
|
371
401
|
}
|
|
372
|
-
get connectionMode() {
|
|
373
|
-
|
|
402
|
+
get connectionMode() {
|
|
403
|
+
return this._deltaManager.connectionManager.connectionMode;
|
|
404
|
+
}
|
|
405
|
+
get IFluidRouter() {
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
374
408
|
get resolvedUrl() {
|
|
375
409
|
return this._resolvedUrl;
|
|
376
410
|
}
|
|
@@ -452,24 +486,39 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
452
486
|
get isDirty() {
|
|
453
487
|
return this._dirtyContainer;
|
|
454
488
|
}
|
|
455
|
-
get serviceFactory() {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
get
|
|
489
|
+
get serviceFactory() {
|
|
490
|
+
return this.loader.services.documentServiceFactory;
|
|
491
|
+
}
|
|
492
|
+
get urlResolver() {
|
|
493
|
+
return this.loader.services.urlResolver;
|
|
494
|
+
}
|
|
495
|
+
get scope() {
|
|
496
|
+
return this.loader.services.scope;
|
|
497
|
+
}
|
|
498
|
+
get codeLoader() {
|
|
499
|
+
return this.loader.services.codeLoader;
|
|
500
|
+
}
|
|
459
501
|
/**
|
|
460
502
|
* Retrieves the quorum associated with the document
|
|
461
503
|
*/
|
|
462
504
|
getQuorum() {
|
|
463
505
|
return this.protocolHandler.quorum;
|
|
464
506
|
}
|
|
507
|
+
dispose(error) {
|
|
508
|
+
this._deltaManager.close(error, true /* doDispose */);
|
|
509
|
+
this.verifyClosed();
|
|
510
|
+
}
|
|
465
511
|
close(error) {
|
|
466
512
|
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
467
513
|
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
468
514
|
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
469
515
|
// "closing" will lose that info (can also solve by tracking extra state).
|
|
470
516
|
this._deltaManager.close(error);
|
|
517
|
+
this.verifyClosed();
|
|
518
|
+
}
|
|
519
|
+
verifyClosed() {
|
|
471
520
|
assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
472
|
-
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
521
|
+
assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
473
522
|
}
|
|
474
523
|
closeCore(error) {
|
|
475
524
|
var _a, _b, _c;
|
|
@@ -497,7 +546,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
497
546
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
498
547
|
}
|
|
499
548
|
this.emit("closed", error);
|
|
500
|
-
this.removeAllListeners();
|
|
501
549
|
if (this.visibilityEventHandler !== undefined) {
|
|
502
550
|
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
503
551
|
}
|
|
@@ -506,6 +554,45 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
506
554
|
this._lifecycleState = "closed";
|
|
507
555
|
}
|
|
508
556
|
}
|
|
557
|
+
disposeCore(error) {
|
|
558
|
+
var _a, _b, _c;
|
|
559
|
+
assert(!this._disposed, 0x54c /* Container already disposed */);
|
|
560
|
+
this._disposed = true;
|
|
561
|
+
try {
|
|
562
|
+
// Ensure that we raise all key events even if one of these throws
|
|
563
|
+
try {
|
|
564
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
565
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
566
|
+
this.mc.logger.sendTelemetryEvent({
|
|
567
|
+
eventName: "ContainerDispose",
|
|
568
|
+
category: error === undefined ? "generic" : "error",
|
|
569
|
+
}, error);
|
|
570
|
+
// ! Progressing from "closed" to "disposing" is not allowed
|
|
571
|
+
if (this._lifecycleState !== "closed") {
|
|
572
|
+
this._lifecycleState = "disposing";
|
|
573
|
+
}
|
|
574
|
+
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
575
|
+
this.connectionStateHandler.dispose();
|
|
576
|
+
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
577
|
+
this.storageService.dispose();
|
|
578
|
+
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
579
|
+
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
580
|
+
// Driver need to ensure all caches are cleared on critical errors
|
|
581
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
582
|
+
}
|
|
583
|
+
catch (exception) {
|
|
584
|
+
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
585
|
+
}
|
|
586
|
+
this.emit("disposed", error);
|
|
587
|
+
this.removeAllListeners();
|
|
588
|
+
if (this.visibilityEventHandler !== undefined) {
|
|
589
|
+
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
this._lifecycleState = "disposed";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
509
596
|
closeAndGetPendingLocalState() {
|
|
510
597
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
511
598
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
@@ -522,6 +609,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
522
609
|
clientId: this.clientId,
|
|
523
610
|
};
|
|
524
611
|
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
612
|
+
// Only close here as method name suggests
|
|
525
613
|
this.close();
|
|
526
614
|
return JSON.stringify(pendingState);
|
|
527
615
|
}
|
|
@@ -533,13 +621,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
533
621
|
const appSummary = this.context.createSummary();
|
|
534
622
|
const protocolSummary = this.captureProtocolSummary();
|
|
535
623
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
536
|
-
if (this.loader.services.detachedBlobStorage &&
|
|
537
|
-
|
|
624
|
+
if (this.loader.services.detachedBlobStorage &&
|
|
625
|
+
this.loader.services.detachedBlobStorage.size > 0) {
|
|
626
|
+
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
627
|
+
type: SummaryType.Blob,
|
|
628
|
+
content: "true",
|
|
629
|
+
};
|
|
538
630
|
}
|
|
539
631
|
return JSON.stringify(combinedSummary);
|
|
540
632
|
}
|
|
541
633
|
async attach(request) {
|
|
542
634
|
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
635
|
+
var _a;
|
|
543
636
|
if (this._lifecycleState !== "loaded") {
|
|
544
637
|
// pre-0.58 error message: containerNotValidForAttach
|
|
545
638
|
throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -548,8 +641,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
548
641
|
assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
549
642
|
this.attachStarted = true;
|
|
550
643
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
551
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
552
|
-
|
|
644
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
|
|
645
|
+
this.loader.services.detachedBlobStorage.size > 0;
|
|
553
646
|
try {
|
|
554
647
|
assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
555
648
|
let summary;
|
|
@@ -587,7 +680,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
587
680
|
const redirectTable = new Map();
|
|
588
681
|
// if new blobs are added while uploading, upload them too
|
|
589
682
|
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
590
|
-
const newIds = this.loader.services.detachedBlobStorage
|
|
683
|
+
const newIds = this.loader.services.detachedBlobStorage
|
|
684
|
+
.getBlobIds()
|
|
685
|
+
.filter((id) => !redirectTable.has(id));
|
|
591
686
|
for (const id of newIds) {
|
|
592
687
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
593
688
|
const response = await this.storageService.createBlob(blob);
|
|
@@ -609,7 +704,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
609
704
|
this._attachState = AttachState.Attached;
|
|
610
705
|
this.emit("attached");
|
|
611
706
|
if (!this.closed) {
|
|
612
|
-
this.resumeInternal({
|
|
707
|
+
this.resumeInternal({
|
|
708
|
+
fetchOpsFromStorage: false,
|
|
709
|
+
reason: "createDetached",
|
|
710
|
+
});
|
|
613
711
|
}
|
|
614
712
|
}
|
|
615
713
|
catch (error) {
|
|
@@ -620,6 +718,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
620
718
|
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
621
719
|
}
|
|
622
720
|
this.close(newError);
|
|
721
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
623
722
|
throw newError;
|
|
624
723
|
}
|
|
625
724
|
}, { start: true, end: true, cancel: "generic" });
|
|
@@ -708,23 +807,27 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
708
807
|
throw new Error("Proposed code details should be greater than the current");
|
|
709
808
|
}
|
|
710
809
|
}
|
|
711
|
-
return this.protocolHandler.quorum
|
|
810
|
+
return this.protocolHandler.quorum
|
|
811
|
+
.propose("code", codeDetails)
|
|
712
812
|
.then(() => true)
|
|
713
813
|
.catch(() => false);
|
|
714
814
|
}
|
|
715
815
|
async processCodeProposal() {
|
|
816
|
+
var _a;
|
|
716
817
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
717
818
|
await Promise.all([
|
|
718
819
|
this.deltaManager.inbound.pause(),
|
|
719
|
-
this.deltaManager.inboundSignal.pause()
|
|
820
|
+
this.deltaManager.inboundSignal.pause(),
|
|
720
821
|
]);
|
|
721
|
-
if ((await this.context.satisfies(codeDetails) === true)
|
|
822
|
+
if ((await this.context.satisfies(codeDetails)) === true) {
|
|
722
823
|
this.deltaManager.inbound.resume();
|
|
723
824
|
this.deltaManager.inboundSignal.resume();
|
|
724
825
|
return;
|
|
725
826
|
}
|
|
726
827
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
727
|
-
|
|
828
|
+
const error = new GenericError("Existing context does not satisfy incoming proposal");
|
|
829
|
+
this.close(error);
|
|
830
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
728
831
|
}
|
|
729
832
|
async getVersion(version) {
|
|
730
833
|
const versions = await this.storageService.getVersions(version, 1);
|
|
@@ -762,7 +865,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
762
865
|
// connections to same file) in two ways:
|
|
763
866
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
764
867
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
765
|
-
const connectionArgs = {
|
|
868
|
+
const connectionArgs = {
|
|
869
|
+
reason: "DocumentOpen",
|
|
870
|
+
mode: "write",
|
|
871
|
+
fetchOpsFromStorage: false,
|
|
872
|
+
};
|
|
766
873
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
767
874
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
768
875
|
if (loadMode.deltaConnection === undefined) {
|
|
@@ -773,7 +880,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
773
880
|
}
|
|
774
881
|
else {
|
|
775
882
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
776
|
-
this.storageService.connectToService(this.service).catch((error) =>
|
|
883
|
+
this.storageService.connectToService(this.service).catch((error) => {
|
|
884
|
+
var _a;
|
|
885
|
+
this.close(error);
|
|
886
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
887
|
+
});
|
|
777
888
|
}
|
|
778
889
|
this._attachState = AttachState.Attached;
|
|
779
890
|
// Fetch specified snapshot.
|
|
@@ -826,8 +937,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
826
937
|
if (!this.closed) {
|
|
827
938
|
if (opsBeforeReturnP !== undefined) {
|
|
828
939
|
this._deltaManager.inbound.resume();
|
|
829
|
-
await
|
|
830
|
-
await
|
|
940
|
+
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOps" }, async () => opsBeforeReturnP);
|
|
941
|
+
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOpProcessing" }, async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
831
942
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
832
943
|
this._deltaManager.inbound.pause();
|
|
833
944
|
}
|
|
@@ -882,7 +993,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
882
993
|
}
|
|
883
994
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
884
995
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
885
|
-
assert(!!this.loader.services.detachedBlobStorage &&
|
|
996
|
+
assert(!!this.loader.services.detachedBlobStorage &&
|
|
997
|
+
this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
886
998
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
887
999
|
}
|
|
888
1000
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
@@ -929,11 +1041,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
929
1041
|
};
|
|
930
1042
|
if (snapshot !== undefined) {
|
|
931
1043
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
932
|
-
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1044
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1045
|
+
await Promise.all([
|
|
1046
|
+
readAndParse(storage, baseTree.blobs.quorumMembers),
|
|
1047
|
+
readAndParse(storage, baseTree.blobs.quorumProposals),
|
|
1048
|
+
readAndParse(storage, baseTree.blobs.quorumValues),
|
|
1049
|
+
]);
|
|
937
1050
|
}
|
|
938
1051
|
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
939
1052
|
}
|
|
@@ -960,7 +1073,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
960
1073
|
});
|
|
961
1074
|
}
|
|
962
1075
|
this.processCodeProposal().catch((error) => {
|
|
963
|
-
|
|
1076
|
+
var _a;
|
|
1077
|
+
const normalizedError = normalizeError(error);
|
|
1078
|
+
this.close(normalizedError);
|
|
1079
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, normalizedError);
|
|
964
1080
|
throw error;
|
|
965
1081
|
});
|
|
966
1082
|
}
|
|
@@ -1017,7 +1133,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1017
1133
|
if (this.clientDetailsOverride !== undefined) {
|
|
1018
1134
|
merge(client.details, this.clientDetailsOverride);
|
|
1019
1135
|
}
|
|
1020
|
-
client.details.environment = [
|
|
1136
|
+
client.details.environment = [
|
|
1137
|
+
client.details.environment,
|
|
1138
|
+
` loaderVersion:${pkgVersion}`,
|
|
1139
|
+
].join(";");
|
|
1021
1140
|
return client;
|
|
1022
1141
|
}
|
|
1023
1142
|
/**
|
|
@@ -1027,8 +1146,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1027
1146
|
* If it's not true, runtime is not in position to send ops.
|
|
1028
1147
|
*/
|
|
1029
1148
|
activeConnection() {
|
|
1030
|
-
return this.connectionState === ConnectionState.Connected &&
|
|
1031
|
-
this.connectionMode === "write";
|
|
1149
|
+
return (this.connectionState === ConnectionState.Connected && this.connectionMode === "write");
|
|
1032
1150
|
}
|
|
1033
1151
|
createDeltaManager() {
|
|
1034
1152
|
const serviceProvider = () => this.service;
|
|
@@ -1039,13 +1157,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1039
1157
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1040
1158
|
deltaManager.inboundSignal.pause();
|
|
1041
1159
|
deltaManager.on("connect", (details, _opsBehind) => {
|
|
1042
|
-
assert(this.connectionMode === details.mode,
|
|
1160
|
+
assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
1043
1161
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1044
1162
|
});
|
|
1045
1163
|
deltaManager.on("disconnect", (reason) => {
|
|
1046
1164
|
var _a;
|
|
1047
1165
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1048
|
-
this.
|
|
1166
|
+
if (!this.closed) {
|
|
1167
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1168
|
+
}
|
|
1049
1169
|
});
|
|
1050
1170
|
deltaManager.on("throttled", (warning) => {
|
|
1051
1171
|
const warn = warning;
|
|
@@ -1063,6 +1183,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1063
1183
|
deltaManager.on("closed", (error) => {
|
|
1064
1184
|
this.closeCore(error);
|
|
1065
1185
|
});
|
|
1186
|
+
deltaManager.on("disposed", (error) => {
|
|
1187
|
+
this.disposeCore(error);
|
|
1188
|
+
});
|
|
1066
1189
|
return deltaManager;
|
|
1067
1190
|
}
|
|
1068
1191
|
async attachDeltaManagerOpHandler(attributes, prefetchType) {
|
|
@@ -1090,7 +1213,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1090
1213
|
}
|
|
1091
1214
|
else {
|
|
1092
1215
|
if (value === ConnectionState.Connected) {
|
|
1093
|
-
durationFromDisconnected =
|
|
1216
|
+
durationFromDisconnected =
|
|
1217
|
+
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1094
1218
|
durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1095
1219
|
}
|
|
1096
1220
|
else {
|
|
@@ -1133,19 +1257,26 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1133
1257
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1134
1258
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1135
1259
|
if (logOpsOnReconnect) {
|
|
1136
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1260
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1261
|
+
eventName: "OpsSentOnReconnect",
|
|
1262
|
+
count: this.messageCountAfterDisconnection,
|
|
1263
|
+
});
|
|
1137
1264
|
}
|
|
1138
1265
|
}
|
|
1139
1266
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1140
1267
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1268
|
+
var _a;
|
|
1141
1269
|
switch (type) {
|
|
1142
1270
|
case MessageType.Operation:
|
|
1143
1271
|
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1144
1272
|
case MessageType.Summarize:
|
|
1145
1273
|
return this.submitSummaryMessage(contents);
|
|
1146
|
-
default:
|
|
1147
|
-
|
|
1274
|
+
default: {
|
|
1275
|
+
const newError = new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
|
|
1276
|
+
this.close(newError);
|
|
1277
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
1148
1278
|
return -1;
|
|
1279
|
+
}
|
|
1149
1280
|
}
|
|
1150
1281
|
}
|
|
1151
1282
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
@@ -1166,8 +1297,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1166
1297
|
if (summary.details === undefined) {
|
|
1167
1298
|
summary.details = {};
|
|
1168
1299
|
}
|
|
1169
|
-
summary.details.includesProtocolTree =
|
|
1170
|
-
this.options.summarizeProtocolTree === true;
|
|
1300
|
+
summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
|
|
1171
1301
|
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1172
1302
|
}
|
|
1173
1303
|
submitMessage(type, contents, batch, metadata, compression) {
|
|
@@ -1226,10 +1356,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1226
1356
|
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1227
1357
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1228
1358
|
// We should have a defined version to load from if specified version requested
|
|
1229
|
-
this.mc.logger.sendErrorEvent({
|
|
1359
|
+
this.mc.logger.sendErrorEvent({
|
|
1360
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
1361
|
+
id: specifiedVersion,
|
|
1362
|
+
});
|
|
1230
1363
|
}
|
|
1231
1364
|
this._loadedFromVersion = version;
|
|
1232
|
-
const snapshot = (_a = await this.storageService.getSnapshotTree(version)) !== null && _a !== void 0 ? _a : undefined;
|
|
1365
|
+
const snapshot = (_a = (await this.storageService.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1233
1366
|
if (snapshot === undefined && version !== undefined) {
|
|
1234
1367
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1235
1368
|
}
|
|
@@ -1248,7 +1381,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1248
1381
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1249
1382
|
// are set. Global requests will still go directly to the loader
|
|
1250
1383
|
const loader = new RelativeLoader(this, this.loader);
|
|
1251
|
-
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);
|
|
1384
|
+
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) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1252
1385
|
this.emit("contextChanged", codeDetails);
|
|
1253
1386
|
}
|
|
1254
1387
|
updateDirtyContainerState(dirty) {
|