@fluidframework/container-loader 2.0.0-dev.2.3.0.115467 → 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.
Files changed (155) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +58 -40
  4. package/api-extractor.json +2 -2
  5. package/dist/audience.d.ts +0 -1
  6. package/dist/audience.d.ts.map +1 -1
  7. package/dist/audience.js.map +1 -1
  8. package/dist/catchUpMonitor.d.ts.map +1 -1
  9. package/dist/catchUpMonitor.js.map +1 -1
  10. package/dist/collabWindowTracker.d.ts.map +1 -1
  11. package/dist/collabWindowTracker.js.map +1 -1
  12. package/dist/connectionManager.d.ts +5 -5
  13. package/dist/connectionManager.d.ts.map +1 -1
  14. package/dist/connectionManager.js +64 -33
  15. package/dist/connectionManager.js.map +1 -1
  16. package/dist/connectionState.d.ts.map +1 -1
  17. package/dist/connectionState.js.map +1 -1
  18. package/dist/connectionStateHandler.d.ts +3 -3
  19. package/dist/connectionStateHandler.d.ts.map +1 -1
  20. package/dist/connectionStateHandler.js +43 -21
  21. package/dist/connectionStateHandler.js.map +1 -1
  22. package/dist/container.d.ts +20 -1
  23. package/dist/container.d.ts.map +1 -1
  24. package/dist/container.js +187 -54
  25. package/dist/container.js.map +1 -1
  26. package/dist/containerContext.d.ts +3 -2
  27. package/dist/containerContext.d.ts.map +1 -1
  28. package/dist/containerContext.js +11 -10
  29. package/dist/containerContext.js.map +1 -1
  30. package/dist/containerStorageAdapter.d.ts.map +1 -1
  31. package/dist/containerStorageAdapter.js +2 -4
  32. package/dist/containerStorageAdapter.js.map +1 -1
  33. package/dist/contracts.d.ts +1 -1
  34. package/dist/contracts.d.ts.map +1 -1
  35. package/dist/contracts.js.map +1 -1
  36. package/dist/deltaManager.d.ts +18 -6
  37. package/dist/deltaManager.d.ts.map +1 -1
  38. package/dist/deltaManager.js +108 -35
  39. package/dist/deltaManager.js.map +1 -1
  40. package/dist/deltaManagerProxy.d.ts.map +1 -1
  41. package/dist/deltaManagerProxy.js.map +1 -1
  42. package/dist/deltaQueue.d.ts.map +1 -1
  43. package/dist/deltaQueue.js +4 -2
  44. package/dist/deltaQueue.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/loader.d.ts +3 -3
  49. package/dist/loader.d.ts.map +1 -1
  50. package/dist/loader.js +16 -8
  51. package/dist/loader.js.map +1 -1
  52. package/dist/packageVersion.d.ts +1 -1
  53. package/dist/packageVersion.js +1 -1
  54. package/dist/packageVersion.js.map +1 -1
  55. package/dist/protocol.d.ts.map +1 -1
  56. package/dist/protocol.js +2 -1
  57. package/dist/protocol.js.map +1 -1
  58. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  59. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  60. package/dist/quorum.d.ts.map +1 -1
  61. package/dist/quorum.js.map +1 -1
  62. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  63. package/dist/retriableDocumentStorageService.js +6 -2
  64. package/dist/retriableDocumentStorageService.js.map +1 -1
  65. package/dist/utils.d.ts.map +1 -1
  66. package/dist/utils.js +6 -4
  67. package/dist/utils.js.map +1 -1
  68. package/lib/audience.d.ts +0 -1
  69. package/lib/audience.d.ts.map +1 -1
  70. package/lib/audience.js.map +1 -1
  71. package/lib/catchUpMonitor.d.ts.map +1 -1
  72. package/lib/catchUpMonitor.js.map +1 -1
  73. package/lib/collabWindowTracker.d.ts.map +1 -1
  74. package/lib/collabWindowTracker.js.map +1 -1
  75. package/lib/connectionManager.d.ts +5 -5
  76. package/lib/connectionManager.d.ts.map +1 -1
  77. package/lib/connectionManager.js +66 -35
  78. package/lib/connectionManager.js.map +1 -1
  79. package/lib/connectionState.d.ts.map +1 -1
  80. package/lib/connectionState.js.map +1 -1
  81. package/lib/connectionStateHandler.d.ts +3 -3
  82. package/lib/connectionStateHandler.d.ts.map +1 -1
  83. package/lib/connectionStateHandler.js +43 -21
  84. package/lib/connectionStateHandler.js.map +1 -1
  85. package/lib/container.d.ts +20 -1
  86. package/lib/container.d.ts.map +1 -1
  87. package/lib/container.js +191 -58
  88. package/lib/container.js.map +1 -1
  89. package/lib/containerContext.d.ts +3 -2
  90. package/lib/containerContext.d.ts.map +1 -1
  91. package/lib/containerContext.js +11 -10
  92. package/lib/containerContext.js.map +1 -1
  93. package/lib/containerStorageAdapter.d.ts.map +1 -1
  94. package/lib/containerStorageAdapter.js +2 -4
  95. package/lib/containerStorageAdapter.js.map +1 -1
  96. package/lib/contracts.d.ts +1 -1
  97. package/lib/contracts.d.ts.map +1 -1
  98. package/lib/contracts.js.map +1 -1
  99. package/lib/deltaManager.d.ts +18 -6
  100. package/lib/deltaManager.d.ts.map +1 -1
  101. package/lib/deltaManager.js +110 -37
  102. package/lib/deltaManager.js.map +1 -1
  103. package/lib/deltaManagerProxy.d.ts.map +1 -1
  104. package/lib/deltaManagerProxy.js.map +1 -1
  105. package/lib/deltaQueue.d.ts.map +1 -1
  106. package/lib/deltaQueue.js +4 -2
  107. package/lib/deltaQueue.js.map +1 -1
  108. package/lib/index.d.ts +1 -1
  109. package/lib/index.d.ts.map +1 -1
  110. package/lib/index.js.map +1 -1
  111. package/lib/loader.d.ts +3 -3
  112. package/lib/loader.d.ts.map +1 -1
  113. package/lib/loader.js +16 -8
  114. package/lib/loader.js.map +1 -1
  115. package/lib/packageVersion.d.ts +1 -1
  116. package/lib/packageVersion.js +1 -1
  117. package/lib/packageVersion.js.map +1 -1
  118. package/lib/protocol.d.ts.map +1 -1
  119. package/lib/protocol.js +2 -1
  120. package/lib/protocol.js.map +1 -1
  121. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  122. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  123. package/lib/quorum.d.ts.map +1 -1
  124. package/lib/quorum.js.map +1 -1
  125. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  126. package/lib/retriableDocumentStorageService.js +6 -2
  127. package/lib/retriableDocumentStorageService.js.map +1 -1
  128. package/lib/utils.d.ts.map +1 -1
  129. package/lib/utils.js +6 -4
  130. package/lib/utils.js.map +1 -1
  131. package/package.json +22 -19
  132. package/prettier.config.cjs +1 -1
  133. package/src/audience.ts +51 -46
  134. package/src/catchUpMonitor.ts +39 -37
  135. package/src/collabWindowTracker.ts +75 -70
  136. package/src/connectionManager.ts +1009 -941
  137. package/src/connectionState.ts +19 -19
  138. package/src/connectionStateHandler.ts +544 -462
  139. package/src/container.ts +2039 -1784
  140. package/src/containerContext.ts +353 -344
  141. package/src/containerStorageAdapter.ts +163 -153
  142. package/src/contracts.ts +155 -153
  143. package/src/deltaManager.ts +1069 -945
  144. package/src/deltaManagerProxy.ts +143 -137
  145. package/src/deltaQueue.ts +155 -151
  146. package/src/index.ts +14 -17
  147. package/src/loader.ts +427 -422
  148. package/src/packageVersion.ts +1 -1
  149. package/src/protocol.ts +93 -87
  150. package/src/protocolTreeDocumentStorageService.ts +30 -33
  151. package/src/quorum.ts +34 -34
  152. package/src/retriableDocumentStorageService.ts +118 -102
  153. package/src/utils.ts +89 -82
  154. package/tsconfig.esnext.json +6 -6
  155. package/tsconfig.json +8 -12
package/dist/container.js CHANGED
@@ -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
- || container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
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 (1s)
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
@@ -141,6 +141,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
141
141
  // Tells if container can reconnect on losing fist connection
142
142
  // If false, container gets closed on loss of connection.
143
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
+ */
144
159
  this._lifecycleState = "loading";
145
160
  this._attachState = container_definitions_1.AttachState.Detached;
146
161
  /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
@@ -151,6 +166,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
151
166
  this.attachStarted = false;
152
167
  this._dirtyContainer = false;
153
168
  this.setAutoReconnectTime = common_utils_1.performance.now();
169
+ this._disposed = false;
154
170
  this.clientDetailsOverride = config.clientDetailsOverride;
155
171
  this._resolvedUrl = config.resolvedUrl;
156
172
  if (config.canReconnect !== undefined) {
@@ -204,17 +220,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
204
220
  }
205
221
  this.logConnectionStateChangeTelemetry(value, oldState, reason);
206
222
  if (this._lifecycleState === "loaded") {
207
- this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected ? reason : undefined /* disconnectedReason */);
223
+ this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
224
+ ? reason
225
+ : undefined /* disconnectedReason */);
208
226
  }
209
227
  },
210
228
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
211
229
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
212
- logConnectionIssue: (eventName, details) => {
230
+ logConnectionIssue: (eventName, category, details) => {
213
231
  const mode = this.connectionMode;
214
232
  // We get here when socket does not receive any ops on "write" connection, including
215
- // its own join op. Attempt recovery option.
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.
216
237
  this._deltaManager.logConnectionIssue(Object.assign({ eventName,
217
- mode, duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
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) })));
218
240
  // If this is "write" connection, it took too long to receive join op. But in most cases that's due
219
241
  // to very slow op fetches and we will eventually get there.
220
242
  // For "read" connections, we get here due to self join signal not arriving on time. We will need to
@@ -248,7 +270,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
248
270
  }
249
271
  else {
250
272
  // settimeout so this will hopefully fire after disconnect event if being hidden caused it
251
- setTimeout(() => { this.lastVisible = undefined; }, 0);
273
+ setTimeout(() => {
274
+ this.lastVisible = undefined;
275
+ }, 0);
252
276
  }
253
277
  };
254
278
  document.addEventListener("visibilitychange", this.visibilityEventHandler);
@@ -259,7 +283,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
259
283
  // if we are in connecting stage.
260
284
  this.on("newListener", (event, listener) => {
261
285
  // Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
262
- Promise.resolve().then(() => {
286
+ Promise.resolve()
287
+ .then(() => {
263
288
  switch (event) {
264
289
  case dirtyContainerEvent:
265
290
  if (this._dirtyContainer) {
@@ -283,7 +308,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
283
308
  break;
284
309
  default:
285
310
  }
286
- }).catch((error) => {
311
+ })
312
+ .catch((error) => {
287
313
  this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
288
314
  });
289
315
  });
@@ -311,7 +337,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
311
337
  reject(err !== null && err !== void 0 ? err : new container_utils_1.GenericError("Container closed without error during load"));
312
338
  };
313
339
  container.on("closed", onClosed);
314
- container.load(version, mode, pendingLocalState)
340
+ container
341
+ .load(version, mode, pendingLocalState)
315
342
  .finally(() => {
316
343
  container.removeListener("closed", onClosed);
317
344
  })
@@ -360,7 +387,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
360
387
  }
361
388
  }
362
389
  get closed() {
363
- return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
390
+ return (this._lifecycleState === "closing" ||
391
+ this._lifecycleState === "closed" ||
392
+ this._lifecycleState === "disposing" ||
393
+ this._lifecycleState === "disposed");
364
394
  }
365
395
  get storage() {
366
396
  return this.storageService;
@@ -377,8 +407,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
377
407
  }
378
408
  return this._protocolHandler;
379
409
  }
380
- get connectionMode() { return this._deltaManager.connectionManager.connectionMode; }
381
- get IFluidRouter() { return this; }
410
+ get connectionMode() {
411
+ return this._deltaManager.connectionManager.connectionMode;
412
+ }
413
+ get IFluidRouter() {
414
+ return this;
415
+ }
382
416
  get resolvedUrl() {
383
417
  return this._resolvedUrl;
384
418
  }
@@ -460,24 +494,39 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
460
494
  get isDirty() {
461
495
  return this._dirtyContainer;
462
496
  }
463
- get serviceFactory() { return this.loader.services.documentServiceFactory; }
464
- get urlResolver() { return this.loader.services.urlResolver; }
465
- get scope() { return this.loader.services.scope; }
466
- get codeLoader() { return this.loader.services.codeLoader; }
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
+ }
467
509
  /**
468
510
  * Retrieves the quorum associated with the document
469
511
  */
470
512
  getQuorum() {
471
513
  return this.protocolHandler.quorum;
472
514
  }
515
+ dispose(error) {
516
+ this._deltaManager.close(error, true /* doDispose */);
517
+ this.verifyClosed();
518
+ }
473
519
  close(error) {
474
520
  // 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
475
521
  // 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
476
522
  // handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
477
523
  // "closing" will lose that info (can also solve by tracking extra state).
478
524
  this._deltaManager.close(error);
525
+ this.verifyClosed();
526
+ }
527
+ verifyClosed() {
479
528
  (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
480
- (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 */);
481
530
  }
482
531
  closeCore(error) {
483
532
  var _a, _b, _c;
@@ -505,7 +554,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
505
554
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
506
555
  }
507
556
  this.emit("closed", error);
508
- this.removeAllListeners();
509
557
  if (this.visibilityEventHandler !== undefined) {
510
558
  document.removeEventListener("visibilitychange", this.visibilityEventHandler);
511
559
  }
@@ -514,6 +562,45 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
514
562
  this._lifecycleState = "closed";
515
563
  }
516
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
+ }
517
604
  closeAndGetPendingLocalState() {
518
605
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
519
606
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
@@ -530,6 +617,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
530
617
  clientId: this.clientId,
531
618
  };
532
619
  this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
620
+ // Only close here as method name suggests
533
621
  this.close();
534
622
  return JSON.stringify(pendingState);
535
623
  }
@@ -541,13 +629,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
541
629
  const appSummary = this.context.createSummary();
542
630
  const protocolSummary = this.captureProtocolSummary();
543
631
  const combinedSummary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
544
- if (this.loader.services.detachedBlobStorage && this.loader.services.detachedBlobStorage.size > 0) {
545
- combinedSummary.tree[".hasAttachmentBlobs"] = { type: protocol_definitions_1.SummaryType.Blob, content: "true" };
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
+ };
546
638
  }
547
639
  return JSON.stringify(combinedSummary);
548
640
  }
549
641
  async attach(request) {
550
642
  await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
643
+ var _a;
551
644
  if (this._lifecycleState !== "loaded") {
552
645
  // pre-0.58 error message: containerNotValidForAttach
553
646
  throw new container_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
@@ -556,8 +649,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
556
649
  (0, common_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
557
650
  this.attachStarted = true;
558
651
  // If attachment blobs were uploaded in detached state we will go through a different attach flow
559
- const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
560
- && this.loader.services.detachedBlobStorage.size > 0;
652
+ const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
653
+ this.loader.services.detachedBlobStorage.size > 0;
561
654
  try {
562
655
  (0, common_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
563
656
  let summary;
@@ -595,7 +688,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
595
688
  const redirectTable = new Map();
596
689
  // if new blobs are added while uploading, upload them too
597
690
  while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
598
- const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter((id) => !redirectTable.has(id));
691
+ const newIds = this.loader.services.detachedBlobStorage
692
+ .getBlobIds()
693
+ .filter((id) => !redirectTable.has(id));
599
694
  for (const id of newIds) {
600
695
  const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
601
696
  const response = await this.storageService.createBlob(blob);
@@ -617,7 +712,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
617
712
  this._attachState = container_definitions_1.AttachState.Attached;
618
713
  this.emit("attached");
619
714
  if (!this.closed) {
620
- this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
715
+ this.resumeInternal({
716
+ fetchOpsFromStorage: false,
717
+ reason: "createDetached",
718
+ });
621
719
  }
622
720
  }
623
721
  catch (error) {
@@ -628,6 +726,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
628
726
  newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
629
727
  }
630
728
  this.close(newError);
729
+ (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
631
730
  throw newError;
632
731
  }
633
732
  }, { start: true, end: true, cancel: "generic" });
@@ -716,23 +815,27 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
716
815
  throw new Error("Proposed code details should be greater than the current");
717
816
  }
718
817
  }
719
- return this.protocolHandler.quorum.propose("code", codeDetails)
818
+ return this.protocolHandler.quorum
819
+ .propose("code", codeDetails)
720
820
  .then(() => true)
721
821
  .catch(() => false);
722
822
  }
723
823
  async processCodeProposal() {
824
+ var _a;
724
825
  const codeDetails = this.getCodeDetailsFromQuorum();
725
826
  await Promise.all([
726
827
  this.deltaManager.inbound.pause(),
727
- this.deltaManager.inboundSignal.pause()
828
+ this.deltaManager.inboundSignal.pause(),
728
829
  ]);
729
- if ((await this.context.satisfies(codeDetails) === true)) {
830
+ if ((await this.context.satisfies(codeDetails)) === true) {
730
831
  this.deltaManager.inbound.resume();
731
832
  this.deltaManager.inboundSignal.resume();
732
833
  return;
733
834
  }
734
835
  // pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
735
- this.close(new container_utils_1.GenericError("Existing context does not satisfy incoming proposal"));
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);
736
839
  }
737
840
  async getVersion(version) {
738
841
  const versions = await this.storageService.getVersions(version, 1);
@@ -770,7 +873,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
770
873
  // connections to same file) in two ways:
771
874
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
772
875
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
773
- const connectionArgs = { reason: "DocumentOpen", mode: "write", fetchOpsFromStorage: false };
876
+ const connectionArgs = {
877
+ reason: "DocumentOpen",
878
+ mode: "write",
879
+ fetchOpsFromStorage: false,
880
+ };
774
881
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
775
882
  // DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
776
883
  if (loadMode.deltaConnection === undefined) {
@@ -781,7 +888,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
781
888
  }
782
889
  else {
783
890
  // if we have pendingLocalState we can load without storage; don't wait for connection
784
- this.storageService.connectToService(this.service).catch((error) => this.close(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
+ });
785
896
  }
786
897
  this._attachState = container_definitions_1.AttachState.Attached;
787
898
  // Fetch specified snapshot.
@@ -834,8 +945,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
834
945
  if (!this.closed) {
835
946
  if (opsBeforeReturnP !== undefined) {
836
947
  this._deltaManager.inbound.resume();
837
- await ReportIfTooLong(this.mc.logger, "WaitOps", async () => { await opsBeforeReturnP; return {}; });
838
- await ReportIfTooLong(this.mc.logger, "WaitOpProcessing", async () => this._deltaManager.inbound.waitTillProcessingDone());
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());
839
950
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
840
951
  this._deltaManager.inbound.pause();
841
952
  }
@@ -890,7 +1001,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
890
1001
  }
891
1002
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
892
1003
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
893
- (0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage && this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
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" */);
894
1006
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
895
1007
  }
896
1008
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
@@ -937,11 +1049,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
937
1049
  };
938
1050
  if (snapshot !== undefined) {
939
1051
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshot);
940
- [quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
941
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
942
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
943
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
944
- ]);
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
+ ]);
945
1058
  }
946
1059
  this.initializeProtocolState(attributes, quorumSnapshot);
947
1060
  }
@@ -968,7 +1081,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
968
1081
  });
969
1082
  }
970
1083
  this.processCodeProposal().catch((error) => {
971
- this.close((0, telemetry_utils_1.normalizeError)(error));
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);
972
1088
  throw error;
973
1089
  });
974
1090
  }
@@ -1025,7 +1141,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1025
1141
  if (this.clientDetailsOverride !== undefined) {
1026
1142
  (0, merge_1.default)(client.details, this.clientDetailsOverride);
1027
1143
  }
1028
- client.details.environment = [client.details.environment, ` loaderVersion:${packageVersion_1.pkgVersion}`].join(";");
1144
+ client.details.environment = [
1145
+ client.details.environment,
1146
+ ` loaderVersion:${packageVersion_1.pkgVersion}`,
1147
+ ].join(";");
1029
1148
  return client;
1030
1149
  }
1031
1150
  /**
@@ -1035,8 +1154,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1035
1154
  * If it's not true, runtime is not in position to send ops.
1036
1155
  */
1037
1156
  activeConnection() {
1038
- return this.connectionState === connectionState_1.ConnectionState.Connected &&
1039
- this.connectionMode === "write";
1157
+ return (this.connectionState === connectionState_1.ConnectionState.Connected && this.connectionMode === "write");
1040
1158
  }
1041
1159
  createDeltaManager() {
1042
1160
  const serviceProvider = () => this.service;
@@ -1053,7 +1171,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1053
1171
  deltaManager.on("disconnect", (reason) => {
1054
1172
  var _a;
1055
1173
  (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1056
- this.connectionStateHandler.receivedDisconnectEvent(reason);
1174
+ if (!this.closed) {
1175
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1176
+ }
1057
1177
  });
1058
1178
  deltaManager.on("throttled", (warning) => {
1059
1179
  const warn = warning;
@@ -1071,6 +1191,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1071
1191
  deltaManager.on("closed", (error) => {
1072
1192
  this.closeCore(error);
1073
1193
  });
1194
+ deltaManager.on("disposed", (error) => {
1195
+ this.disposeCore(error);
1196
+ });
1074
1197
  return deltaManager;
1075
1198
  }
1076
1199
  async attachDeltaManagerOpHandler(attributes, prefetchType) {
@@ -1098,7 +1221,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1098
1221
  }
1099
1222
  else {
1100
1223
  if (value === connectionState_1.ConnectionState.Connected) {
1101
- durationFromDisconnected = time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
1224
+ durationFromDisconnected =
1225
+ time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
1102
1226
  durationFromDisconnected = telemetry_utils_1.TelemetryLogger.formatTick(durationFromDisconnected);
1103
1227
  }
1104
1228
  else {
@@ -1141,19 +1265,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1141
1265
  this.protocolHandler.setConnectionState(state, this.clientId);
1142
1266
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
1143
1267
  if (logOpsOnReconnect) {
1144
- this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1268
+ this.mc.logger.sendTelemetryEvent({
1269
+ eventName: "OpsSentOnReconnect",
1270
+ count: this.messageCountAfterDisconnection,
1271
+ });
1145
1272
  }
1146
1273
  }
1147
1274
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1148
1275
  submitContainerMessage(type, contents, batch, metadata) {
1276
+ var _a;
1149
1277
  switch (type) {
1150
1278
  case protocol_definitions_1.MessageType.Operation:
1151
1279
  return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1152
1280
  case protocol_definitions_1.MessageType.Summarize:
1153
1281
  return this.submitSummaryMessage(contents);
1154
- default:
1155
- this.close(new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
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);
1156
1286
  return -1;
1287
+ }
1157
1288
  }
1158
1289
  }
1159
1290
  /** @returns clientSequenceNumber of last message in a batch */
@@ -1174,8 +1305,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1174
1305
  if (summary.details === undefined) {
1175
1306
  summary.details = {};
1176
1307
  }
1177
- summary.details.includesProtocolTree =
1178
- this.options.summarizeProtocolTree === true;
1308
+ summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
1179
1309
  return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1180
1310
  }
1181
1311
  submitMessage(type, contents, batch, metadata, compression) {
@@ -1234,10 +1364,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1234
1364
  const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
1235
1365
  if (version === undefined && specifiedVersion !== undefined) {
1236
1366
  // We should have a defined version to load from if specified version requested
1237
- this.mc.logger.sendErrorEvent({ eventName: "NoVersionFoundWhenSpecified", id: specifiedVersion });
1367
+ this.mc.logger.sendErrorEvent({
1368
+ eventName: "NoVersionFoundWhenSpecified",
1369
+ id: specifiedVersion,
1370
+ });
1238
1371
  }
1239
1372
  this._loadedFromVersion = version;
1240
- 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;
1241
1374
  if (snapshot === undefined && version !== undefined) {
1242
1375
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1243
1376
  }
@@ -1256,7 +1389,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1256
1389
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1257
1390
  // are set. Global requests will still go directly to the loader
1258
1391
  const loader = new loader_1.RelativeLoader(this, this.loader);
1259
- 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);
1260
1393
  this.emit("contextChanged", codeDetails);
1261
1394
  }
1262
1395
  updateDirtyContainerState(dirty) {