@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/dist/container.js
CHANGED
|
@@ -7,7 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.Container = exports.waitContainerToCatchUp = void 0;
|
|
10
|
+
exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = void 0;
|
|
11
11
|
// eslint-disable-next-line import/no-internal-modules
|
|
12
12
|
const merge_1 = __importDefault(require("lodash/merge"));
|
|
13
13
|
const uuid_1 = require("uuid");
|
|
@@ -71,8 +71,8 @@ async function waitContainerToCatchUp(container) {
|
|
|
71
71
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
72
72
|
// which is a reasonable approximation of "caught up"
|
|
73
73
|
const waitForOps = () => {
|
|
74
|
-
(0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp
|
|
75
|
-
|
|
74
|
+
(0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp ||
|
|
75
|
+
container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
76
76
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
77
77
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
78
78
|
(0, common_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -113,7 +113,7 @@ const getCodeProposal =
|
|
|
113
113
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
114
114
|
(quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
|
|
115
115
|
/**
|
|
116
|
-
* Helper function to report to telemetry cases where operation takes longer than expected (
|
|
116
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
117
117
|
* @param logger - logger to use
|
|
118
118
|
* @param eventName - event name
|
|
119
119
|
* @param action - functor to call and measure
|
|
@@ -121,10 +121,11 @@ const getCodeProposal =
|
|
|
121
121
|
async function ReportIfTooLong(logger, eventName, action) {
|
|
122
122
|
const event = telemetry_utils_1.PerformanceEvent.start(logger, { eventName });
|
|
123
123
|
const props = await action();
|
|
124
|
-
if (event.duration >
|
|
124
|
+
if (event.duration > 200) {
|
|
125
125
|
event.end(props);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
+
exports.ReportIfTooLong = ReportIfTooLong;
|
|
128
129
|
const summarizerClientType = "summarizer";
|
|
129
130
|
class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
130
131
|
constructor(loader, config, protocolHandlerBuilder) {
|
|
@@ -140,6 +141,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
140
141
|
// Tells if container can reconnect on losing fist connection
|
|
141
142
|
// If false, container gets closed on loss of connection.
|
|
142
143
|
this._canReconnect = true;
|
|
144
|
+
/**
|
|
145
|
+
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
146
|
+
*
|
|
147
|
+
* States are allowed to progress to further states:
|
|
148
|
+
* "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
|
|
149
|
+
*
|
|
150
|
+
* For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
|
|
151
|
+
*
|
|
152
|
+
* loading: Container has been created, but is not yet in normal/loaded state
|
|
153
|
+
* loaded: Container is in normal/loaded state
|
|
154
|
+
* closing: Container has started closing process (for re-entrancy prevention)
|
|
155
|
+
* disposing: Container has started disposing process (for re-entrancy prevention)
|
|
156
|
+
* closed: Container has closed
|
|
157
|
+
* disposed: Container has been disposed
|
|
158
|
+
*/
|
|
143
159
|
this._lifecycleState = "loading";
|
|
144
160
|
this._attachState = container_definitions_1.AttachState.Detached;
|
|
145
161
|
/** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
|
|
@@ -150,6 +166,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
150
166
|
this.attachStarted = false;
|
|
151
167
|
this._dirtyContainer = false;
|
|
152
168
|
this.setAutoReconnectTime = common_utils_1.performance.now();
|
|
169
|
+
this._disposed = false;
|
|
153
170
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
154
171
|
this._resolvedUrl = config.resolvedUrl;
|
|
155
172
|
if (config.canReconnect !== undefined) {
|
|
@@ -203,17 +220,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
203
220
|
}
|
|
204
221
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
205
222
|
if (this._lifecycleState === "loaded") {
|
|
206
|
-
this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
|
|
223
|
+
this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
|
|
224
|
+
? reason
|
|
225
|
+
: undefined /* disconnectedReason */);
|
|
207
226
|
}
|
|
208
227
|
},
|
|
209
228
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
210
229
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
211
|
-
logConnectionIssue: (eventName, details) => {
|
|
230
|
+
logConnectionIssue: (eventName, category, details) => {
|
|
212
231
|
const mode = this.connectionMode;
|
|
213
232
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
214
|
-
// its own join op.
|
|
233
|
+
// its own join op.
|
|
234
|
+
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
235
|
+
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
236
|
+
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
215
237
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName,
|
|
216
|
-
mode,
|
|
238
|
+
mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: common_utils_1.performance.now() -
|
|
239
|
+
this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
217
240
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
218
241
|
// to very slow op fetches and we will eventually get there.
|
|
219
242
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -247,7 +270,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
247
270
|
}
|
|
248
271
|
else {
|
|
249
272
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
250
|
-
setTimeout(() => {
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.lastVisible = undefined;
|
|
275
|
+
}, 0);
|
|
251
276
|
}
|
|
252
277
|
};
|
|
253
278
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
@@ -258,7 +283,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
258
283
|
// if we are in connecting stage.
|
|
259
284
|
this.on("newListener", (event, listener) => {
|
|
260
285
|
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
261
|
-
Promise.resolve()
|
|
286
|
+
Promise.resolve()
|
|
287
|
+
.then(() => {
|
|
262
288
|
switch (event) {
|
|
263
289
|
case dirtyContainerEvent:
|
|
264
290
|
if (this._dirtyContainer) {
|
|
@@ -282,7 +308,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
282
308
|
break;
|
|
283
309
|
default:
|
|
284
310
|
}
|
|
285
|
-
})
|
|
311
|
+
})
|
|
312
|
+
.catch((error) => {
|
|
286
313
|
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
287
314
|
});
|
|
288
315
|
});
|
|
@@ -310,7 +337,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
310
337
|
reject(err !== null && err !== void 0 ? err : new container_utils_1.GenericError("Container closed without error during load"));
|
|
311
338
|
};
|
|
312
339
|
container.on("closed", onClosed);
|
|
313
|
-
container
|
|
340
|
+
container
|
|
341
|
+
.load(version, mode, pendingLocalState)
|
|
314
342
|
.finally(() => {
|
|
315
343
|
container.removeListener("closed", onClosed);
|
|
316
344
|
})
|
|
@@ -359,7 +387,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
359
387
|
}
|
|
360
388
|
}
|
|
361
389
|
get closed() {
|
|
362
|
-
return (this._lifecycleState === "closing" ||
|
|
390
|
+
return (this._lifecycleState === "closing" ||
|
|
391
|
+
this._lifecycleState === "closed" ||
|
|
392
|
+
this._lifecycleState === "disposing" ||
|
|
393
|
+
this._lifecycleState === "disposed");
|
|
363
394
|
}
|
|
364
395
|
get storage() {
|
|
365
396
|
return this.storageService;
|
|
@@ -376,8 +407,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
376
407
|
}
|
|
377
408
|
return this._protocolHandler;
|
|
378
409
|
}
|
|
379
|
-
get connectionMode() {
|
|
380
|
-
|
|
410
|
+
get connectionMode() {
|
|
411
|
+
return this._deltaManager.connectionManager.connectionMode;
|
|
412
|
+
}
|
|
413
|
+
get IFluidRouter() {
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
381
416
|
get resolvedUrl() {
|
|
382
417
|
return this._resolvedUrl;
|
|
383
418
|
}
|
|
@@ -459,24 +494,39 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
459
494
|
get isDirty() {
|
|
460
495
|
return this._dirtyContainer;
|
|
461
496
|
}
|
|
462
|
-
get serviceFactory() {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
get
|
|
497
|
+
get serviceFactory() {
|
|
498
|
+
return this.loader.services.documentServiceFactory;
|
|
499
|
+
}
|
|
500
|
+
get urlResolver() {
|
|
501
|
+
return this.loader.services.urlResolver;
|
|
502
|
+
}
|
|
503
|
+
get scope() {
|
|
504
|
+
return this.loader.services.scope;
|
|
505
|
+
}
|
|
506
|
+
get codeLoader() {
|
|
507
|
+
return this.loader.services.codeLoader;
|
|
508
|
+
}
|
|
466
509
|
/**
|
|
467
510
|
* Retrieves the quorum associated with the document
|
|
468
511
|
*/
|
|
469
512
|
getQuorum() {
|
|
470
513
|
return this.protocolHandler.quorum;
|
|
471
514
|
}
|
|
515
|
+
dispose(error) {
|
|
516
|
+
this._deltaManager.close(error, true /* doDispose */);
|
|
517
|
+
this.verifyClosed();
|
|
518
|
+
}
|
|
472
519
|
close(error) {
|
|
473
520
|
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
474
521
|
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
475
522
|
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
476
523
|
// "closing" will lose that info (can also solve by tracking extra state).
|
|
477
524
|
this._deltaManager.close(error);
|
|
525
|
+
this.verifyClosed();
|
|
526
|
+
}
|
|
527
|
+
verifyClosed() {
|
|
478
528
|
(0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
479
|
-
(0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
529
|
+
(0, common_utils_1.assert)(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
480
530
|
}
|
|
481
531
|
closeCore(error) {
|
|
482
532
|
var _a, _b, _c;
|
|
@@ -504,7 +554,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
504
554
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
505
555
|
}
|
|
506
556
|
this.emit("closed", error);
|
|
507
|
-
this.removeAllListeners();
|
|
508
557
|
if (this.visibilityEventHandler !== undefined) {
|
|
509
558
|
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
510
559
|
}
|
|
@@ -513,6 +562,45 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
513
562
|
this._lifecycleState = "closed";
|
|
514
563
|
}
|
|
515
564
|
}
|
|
565
|
+
disposeCore(error) {
|
|
566
|
+
var _a, _b, _c;
|
|
567
|
+
(0, common_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
|
|
568
|
+
this._disposed = true;
|
|
569
|
+
try {
|
|
570
|
+
// Ensure that we raise all key events even if one of these throws
|
|
571
|
+
try {
|
|
572
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
573
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
574
|
+
this.mc.logger.sendTelemetryEvent({
|
|
575
|
+
eventName: "ContainerDispose",
|
|
576
|
+
category: error === undefined ? "generic" : "error",
|
|
577
|
+
}, error);
|
|
578
|
+
// ! Progressing from "closed" to "disposing" is not allowed
|
|
579
|
+
if (this._lifecycleState !== "closed") {
|
|
580
|
+
this._lifecycleState = "disposing";
|
|
581
|
+
}
|
|
582
|
+
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
583
|
+
this.connectionStateHandler.dispose();
|
|
584
|
+
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
585
|
+
this.storageService.dispose();
|
|
586
|
+
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
587
|
+
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
588
|
+
// Driver need to ensure all caches are cleared on critical errors
|
|
589
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
590
|
+
}
|
|
591
|
+
catch (exception) {
|
|
592
|
+
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
593
|
+
}
|
|
594
|
+
this.emit("disposed", error);
|
|
595
|
+
this.removeAllListeners();
|
|
596
|
+
if (this.visibilityEventHandler !== undefined) {
|
|
597
|
+
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
this._lifecycleState = "disposed";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
516
604
|
closeAndGetPendingLocalState() {
|
|
517
605
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
518
606
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
@@ -529,6 +617,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
529
617
|
clientId: this.clientId,
|
|
530
618
|
};
|
|
531
619
|
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
620
|
+
// Only close here as method name suggests
|
|
532
621
|
this.close();
|
|
533
622
|
return JSON.stringify(pendingState);
|
|
534
623
|
}
|
|
@@ -540,13 +629,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
540
629
|
const appSummary = this.context.createSummary();
|
|
541
630
|
const protocolSummary = this.captureProtocolSummary();
|
|
542
631
|
const combinedSummary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
543
|
-
if (this.loader.services.detachedBlobStorage &&
|
|
544
|
-
|
|
632
|
+
if (this.loader.services.detachedBlobStorage &&
|
|
633
|
+
this.loader.services.detachedBlobStorage.size > 0) {
|
|
634
|
+
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
635
|
+
type: protocol_definitions_1.SummaryType.Blob,
|
|
636
|
+
content: "true",
|
|
637
|
+
};
|
|
545
638
|
}
|
|
546
639
|
return JSON.stringify(combinedSummary);
|
|
547
640
|
}
|
|
548
641
|
async attach(request) {
|
|
549
642
|
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
643
|
+
var _a;
|
|
550
644
|
if (this._lifecycleState !== "loaded") {
|
|
551
645
|
// pre-0.58 error message: containerNotValidForAttach
|
|
552
646
|
throw new container_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -555,8 +649,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
555
649
|
(0, common_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
556
650
|
this.attachStarted = true;
|
|
557
651
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
558
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
559
|
-
|
|
652
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
|
|
653
|
+
this.loader.services.detachedBlobStorage.size > 0;
|
|
560
654
|
try {
|
|
561
655
|
(0, common_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
562
656
|
let summary;
|
|
@@ -594,7 +688,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
594
688
|
const redirectTable = new Map();
|
|
595
689
|
// if new blobs are added while uploading, upload them too
|
|
596
690
|
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
597
|
-
const newIds = this.loader.services.detachedBlobStorage
|
|
691
|
+
const newIds = this.loader.services.detachedBlobStorage
|
|
692
|
+
.getBlobIds()
|
|
693
|
+
.filter((id) => !redirectTable.has(id));
|
|
598
694
|
for (const id of newIds) {
|
|
599
695
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
600
696
|
const response = await this.storageService.createBlob(blob);
|
|
@@ -616,7 +712,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
616
712
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
617
713
|
this.emit("attached");
|
|
618
714
|
if (!this.closed) {
|
|
619
|
-
this.resumeInternal({
|
|
715
|
+
this.resumeInternal({
|
|
716
|
+
fetchOpsFromStorage: false,
|
|
717
|
+
reason: "createDetached",
|
|
718
|
+
});
|
|
620
719
|
}
|
|
621
720
|
}
|
|
622
721
|
catch (error) {
|
|
@@ -627,6 +726,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
627
726
|
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
628
727
|
}
|
|
629
728
|
this.close(newError);
|
|
729
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
630
730
|
throw newError;
|
|
631
731
|
}
|
|
632
732
|
}, { start: true, end: true, cancel: "generic" });
|
|
@@ -715,23 +815,27 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
715
815
|
throw new Error("Proposed code details should be greater than the current");
|
|
716
816
|
}
|
|
717
817
|
}
|
|
718
|
-
return this.protocolHandler.quorum
|
|
818
|
+
return this.protocolHandler.quorum
|
|
819
|
+
.propose("code", codeDetails)
|
|
719
820
|
.then(() => true)
|
|
720
821
|
.catch(() => false);
|
|
721
822
|
}
|
|
722
823
|
async processCodeProposal() {
|
|
824
|
+
var _a;
|
|
723
825
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
724
826
|
await Promise.all([
|
|
725
827
|
this.deltaManager.inbound.pause(),
|
|
726
|
-
this.deltaManager.inboundSignal.pause()
|
|
828
|
+
this.deltaManager.inboundSignal.pause(),
|
|
727
829
|
]);
|
|
728
|
-
if ((await this.context.satisfies(codeDetails) === true)
|
|
830
|
+
if ((await this.context.satisfies(codeDetails)) === true) {
|
|
729
831
|
this.deltaManager.inbound.resume();
|
|
730
832
|
this.deltaManager.inboundSignal.resume();
|
|
731
833
|
return;
|
|
732
834
|
}
|
|
733
835
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
734
|
-
|
|
836
|
+
const error = new container_utils_1.GenericError("Existing context does not satisfy incoming proposal");
|
|
837
|
+
this.close(error);
|
|
838
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
735
839
|
}
|
|
736
840
|
async getVersion(version) {
|
|
737
841
|
const versions = await this.storageService.getVersions(version, 1);
|
|
@@ -769,7 +873,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
769
873
|
// connections to same file) in two ways:
|
|
770
874
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
771
875
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
772
|
-
const connectionArgs = {
|
|
876
|
+
const connectionArgs = {
|
|
877
|
+
reason: "DocumentOpen",
|
|
878
|
+
mode: "write",
|
|
879
|
+
fetchOpsFromStorage: false,
|
|
880
|
+
};
|
|
773
881
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
774
882
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
775
883
|
if (loadMode.deltaConnection === undefined) {
|
|
@@ -780,7 +888,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
780
888
|
}
|
|
781
889
|
else {
|
|
782
890
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
783
|
-
this.storageService.connectToService(this.service).catch((error) =>
|
|
891
|
+
this.storageService.connectToService(this.service).catch((error) => {
|
|
892
|
+
var _a;
|
|
893
|
+
this.close(error);
|
|
894
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
895
|
+
});
|
|
784
896
|
}
|
|
785
897
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
786
898
|
// Fetch specified snapshot.
|
|
@@ -833,8 +945,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
833
945
|
if (!this.closed) {
|
|
834
946
|
if (opsBeforeReturnP !== undefined) {
|
|
835
947
|
this._deltaManager.inbound.resume();
|
|
836
|
-
await
|
|
837
|
-
await
|
|
948
|
+
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOps" }, async () => opsBeforeReturnP);
|
|
949
|
+
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOpProcessing" }, async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
838
950
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
839
951
|
this._deltaManager.inbound.pause();
|
|
840
952
|
}
|
|
@@ -889,7 +1001,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
889
1001
|
}
|
|
890
1002
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
891
1003
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
892
|
-
(0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage &&
|
|
1004
|
+
(0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage &&
|
|
1005
|
+
this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
893
1006
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
894
1007
|
}
|
|
895
1008
|
const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
|
|
@@ -936,11 +1049,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
936
1049
|
};
|
|
937
1050
|
if (snapshot !== undefined) {
|
|
938
1051
|
const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshot);
|
|
939
|
-
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1052
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1053
|
+
await Promise.all([
|
|
1054
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
|
|
1055
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
|
|
1056
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
|
|
1057
|
+
]);
|
|
944
1058
|
}
|
|
945
1059
|
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
946
1060
|
}
|
|
@@ -967,7 +1081,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
967
1081
|
});
|
|
968
1082
|
}
|
|
969
1083
|
this.processCodeProposal().catch((error) => {
|
|
970
|
-
|
|
1084
|
+
var _a;
|
|
1085
|
+
const normalizedError = (0, telemetry_utils_1.normalizeError)(error);
|
|
1086
|
+
this.close(normalizedError);
|
|
1087
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, normalizedError);
|
|
971
1088
|
throw error;
|
|
972
1089
|
});
|
|
973
1090
|
}
|
|
@@ -1024,7 +1141,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1024
1141
|
if (this.clientDetailsOverride !== undefined) {
|
|
1025
1142
|
(0, merge_1.default)(client.details, this.clientDetailsOverride);
|
|
1026
1143
|
}
|
|
1027
|
-
client.details.environment = [
|
|
1144
|
+
client.details.environment = [
|
|
1145
|
+
client.details.environment,
|
|
1146
|
+
` loaderVersion:${packageVersion_1.pkgVersion}`,
|
|
1147
|
+
].join(";");
|
|
1028
1148
|
return client;
|
|
1029
1149
|
}
|
|
1030
1150
|
/**
|
|
@@ -1034,8 +1154,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1034
1154
|
* If it's not true, runtime is not in position to send ops.
|
|
1035
1155
|
*/
|
|
1036
1156
|
activeConnection() {
|
|
1037
|
-
return this.connectionState === connectionState_1.ConnectionState.Connected &&
|
|
1038
|
-
this.connectionMode === "write";
|
|
1157
|
+
return (this.connectionState === connectionState_1.ConnectionState.Connected && this.connectionMode === "write");
|
|
1039
1158
|
}
|
|
1040
1159
|
createDeltaManager() {
|
|
1041
1160
|
const serviceProvider = () => this.service;
|
|
@@ -1046,13 +1165,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1046
1165
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1047
1166
|
deltaManager.inboundSignal.pause();
|
|
1048
1167
|
deltaManager.on("connect", (details, _opsBehind) => {
|
|
1049
|
-
(0, common_utils_1.assert)(this.connectionMode === details.mode,
|
|
1168
|
+
(0, common_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
1050
1169
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1051
1170
|
});
|
|
1052
1171
|
deltaManager.on("disconnect", (reason) => {
|
|
1053
1172
|
var _a;
|
|
1054
1173
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1055
|
-
this.
|
|
1174
|
+
if (!this.closed) {
|
|
1175
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1176
|
+
}
|
|
1056
1177
|
});
|
|
1057
1178
|
deltaManager.on("throttled", (warning) => {
|
|
1058
1179
|
const warn = warning;
|
|
@@ -1070,6 +1191,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1070
1191
|
deltaManager.on("closed", (error) => {
|
|
1071
1192
|
this.closeCore(error);
|
|
1072
1193
|
});
|
|
1194
|
+
deltaManager.on("disposed", (error) => {
|
|
1195
|
+
this.disposeCore(error);
|
|
1196
|
+
});
|
|
1073
1197
|
return deltaManager;
|
|
1074
1198
|
}
|
|
1075
1199
|
async attachDeltaManagerOpHandler(attributes, prefetchType) {
|
|
@@ -1097,7 +1221,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1097
1221
|
}
|
|
1098
1222
|
else {
|
|
1099
1223
|
if (value === connectionState_1.ConnectionState.Connected) {
|
|
1100
|
-
durationFromDisconnected =
|
|
1224
|
+
durationFromDisconnected =
|
|
1225
|
+
time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
|
|
1101
1226
|
durationFromDisconnected = telemetry_utils_1.TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1102
1227
|
}
|
|
1103
1228
|
else {
|
|
@@ -1140,19 +1265,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1140
1265
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1141
1266
|
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1142
1267
|
if (logOpsOnReconnect) {
|
|
1143
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1268
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1269
|
+
eventName: "OpsSentOnReconnect",
|
|
1270
|
+
count: this.messageCountAfterDisconnection,
|
|
1271
|
+
});
|
|
1144
1272
|
}
|
|
1145
1273
|
}
|
|
1146
1274
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1147
1275
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1276
|
+
var _a;
|
|
1148
1277
|
switch (type) {
|
|
1149
1278
|
case protocol_definitions_1.MessageType.Operation:
|
|
1150
1279
|
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1151
1280
|
case protocol_definitions_1.MessageType.Summarize:
|
|
1152
1281
|
return this.submitSummaryMessage(contents);
|
|
1153
|
-
default:
|
|
1154
|
-
|
|
1282
|
+
default: {
|
|
1283
|
+
const newError = new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
|
|
1284
|
+
this.close(newError);
|
|
1285
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
1155
1286
|
return -1;
|
|
1287
|
+
}
|
|
1156
1288
|
}
|
|
1157
1289
|
}
|
|
1158
1290
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
@@ -1173,8 +1305,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1173
1305
|
if (summary.details === undefined) {
|
|
1174
1306
|
summary.details = {};
|
|
1175
1307
|
}
|
|
1176
|
-
summary.details.includesProtocolTree =
|
|
1177
|
-
this.options.summarizeProtocolTree === true;
|
|
1308
|
+
summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
|
|
1178
1309
|
return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1179
1310
|
}
|
|
1180
1311
|
submitMessage(type, contents, batch, metadata, compression) {
|
|
@@ -1233,10 +1364,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1233
1364
|
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1234
1365
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1235
1366
|
// We should have a defined version to load from if specified version requested
|
|
1236
|
-
this.mc.logger.sendErrorEvent({
|
|
1367
|
+
this.mc.logger.sendErrorEvent({
|
|
1368
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
1369
|
+
id: specifiedVersion,
|
|
1370
|
+
});
|
|
1237
1371
|
}
|
|
1238
1372
|
this._loadedFromVersion = version;
|
|
1239
|
-
const snapshot = (_a = await this.storageService.getSnapshotTree(version)) !== null && _a !== void 0 ? _a : undefined;
|
|
1373
|
+
const snapshot = (_a = (await this.storageService.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1240
1374
|
if (snapshot === undefined && version !== undefined) {
|
|
1241
1375
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1242
1376
|
}
|
|
@@ -1255,7 +1389,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1255
1389
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1256
1390
|
// are set. Global requests will still go directly to the loader
|
|
1257
1391
|
const loader = new loader_1.RelativeLoader(this, this.loader);
|
|
1258
|
-
this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.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);
|
|
1392
|
+
this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.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);
|
|
1259
1393
|
this.emit("contextChanged", codeDetails);
|
|
1260
1394
|
}
|
|
1261
1395
|
updateDirtyContainerState(dirty) {
|