@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.
Files changed (159) 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 +6 -1
  8. package/dist/audience.js.map +1 -1
  9. package/dist/catchUpMonitor.d.ts.map +1 -1
  10. package/dist/catchUpMonitor.js.map +1 -1
  11. package/dist/collabWindowTracker.d.ts.map +1 -1
  12. package/dist/collabWindowTracker.js +5 -4
  13. package/dist/collabWindowTracker.js.map +1 -1
  14. package/dist/connectionManager.d.ts +5 -5
  15. package/dist/connectionManager.d.ts.map +1 -1
  16. package/dist/connectionManager.js +66 -32
  17. package/dist/connectionManager.js.map +1 -1
  18. package/dist/connectionState.d.ts.map +1 -1
  19. package/dist/connectionState.js.map +1 -1
  20. package/dist/connectionStateHandler.d.ts +3 -3
  21. package/dist/connectionStateHandler.d.ts.map +1 -1
  22. package/dist/connectionStateHandler.js +46 -24
  23. package/dist/connectionStateHandler.js.map +1 -1
  24. package/dist/container.d.ts +27 -0
  25. package/dist/container.d.ts.map +1 -1
  26. package/dist/container.js +191 -57
  27. package/dist/container.js.map +1 -1
  28. package/dist/containerContext.d.ts +3 -2
  29. package/dist/containerContext.d.ts.map +1 -1
  30. package/dist/containerContext.js +11 -6
  31. package/dist/containerContext.js.map +1 -1
  32. package/dist/containerStorageAdapter.d.ts.map +1 -1
  33. package/dist/containerStorageAdapter.js +2 -4
  34. package/dist/containerStorageAdapter.js.map +1 -1
  35. package/dist/contracts.d.ts +9 -1
  36. package/dist/contracts.d.ts.map +1 -1
  37. package/dist/contracts.js.map +1 -1
  38. package/dist/deltaManager.d.ts +18 -6
  39. package/dist/deltaManager.d.ts.map +1 -1
  40. package/dist/deltaManager.js +110 -37
  41. package/dist/deltaManager.js.map +1 -1
  42. package/dist/deltaManagerProxy.d.ts.map +1 -1
  43. package/dist/deltaManagerProxy.js.map +1 -1
  44. package/dist/deltaQueue.d.ts.map +1 -1
  45. package/dist/deltaQueue.js +4 -2
  46. package/dist/deltaQueue.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/loader.d.ts +3 -3
  51. package/dist/loader.d.ts.map +1 -1
  52. package/dist/loader.js +16 -8
  53. package/dist/loader.js.map +1 -1
  54. package/dist/packageVersion.d.ts +1 -1
  55. package/dist/packageVersion.js +1 -1
  56. package/dist/packageVersion.js.map +1 -1
  57. package/dist/protocol.d.ts.map +1 -1
  58. package/dist/protocol.js +2 -1
  59. package/dist/protocol.js.map +1 -1
  60. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  61. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  62. package/dist/quorum.d.ts.map +1 -1
  63. package/dist/quorum.js.map +1 -1
  64. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  65. package/dist/retriableDocumentStorageService.js +6 -2
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/dist/utils.d.ts.map +1 -1
  68. package/dist/utils.js +6 -4
  69. package/dist/utils.js.map +1 -1
  70. package/lib/audience.d.ts +0 -1
  71. package/lib/audience.d.ts.map +1 -1
  72. package/lib/audience.js +6 -1
  73. package/lib/audience.js.map +1 -1
  74. package/lib/catchUpMonitor.d.ts.map +1 -1
  75. package/lib/catchUpMonitor.js.map +1 -1
  76. package/lib/collabWindowTracker.d.ts.map +1 -1
  77. package/lib/collabWindowTracker.js +5 -4
  78. package/lib/collabWindowTracker.js.map +1 -1
  79. package/lib/connectionManager.d.ts +5 -5
  80. package/lib/connectionManager.d.ts.map +1 -1
  81. package/lib/connectionManager.js +68 -34
  82. package/lib/connectionManager.js.map +1 -1
  83. package/lib/connectionState.d.ts.map +1 -1
  84. package/lib/connectionState.js.map +1 -1
  85. package/lib/connectionStateHandler.d.ts +3 -3
  86. package/lib/connectionStateHandler.d.ts.map +1 -1
  87. package/lib/connectionStateHandler.js +46 -24
  88. package/lib/connectionStateHandler.js.map +1 -1
  89. package/lib/container.d.ts +27 -0
  90. package/lib/container.d.ts.map +1 -1
  91. package/lib/container.js +194 -61
  92. package/lib/container.js.map +1 -1
  93. package/lib/containerContext.d.ts +3 -2
  94. package/lib/containerContext.d.ts.map +1 -1
  95. package/lib/containerContext.js +11 -6
  96. package/lib/containerContext.js.map +1 -1
  97. package/lib/containerStorageAdapter.d.ts.map +1 -1
  98. package/lib/containerStorageAdapter.js +2 -4
  99. package/lib/containerStorageAdapter.js.map +1 -1
  100. package/lib/contracts.d.ts +9 -1
  101. package/lib/contracts.d.ts.map +1 -1
  102. package/lib/contracts.js.map +1 -1
  103. package/lib/deltaManager.d.ts +18 -6
  104. package/lib/deltaManager.d.ts.map +1 -1
  105. package/lib/deltaManager.js +112 -39
  106. package/lib/deltaManager.js.map +1 -1
  107. package/lib/deltaManagerProxy.d.ts.map +1 -1
  108. package/lib/deltaManagerProxy.js.map +1 -1
  109. package/lib/deltaQueue.d.ts.map +1 -1
  110. package/lib/deltaQueue.js +4 -2
  111. package/lib/deltaQueue.js.map +1 -1
  112. package/lib/index.d.ts +1 -1
  113. package/lib/index.d.ts.map +1 -1
  114. package/lib/index.js.map +1 -1
  115. package/lib/loader.d.ts +3 -3
  116. package/lib/loader.d.ts.map +1 -1
  117. package/lib/loader.js +16 -8
  118. package/lib/loader.js.map +1 -1
  119. package/lib/packageVersion.d.ts +1 -1
  120. package/lib/packageVersion.js +1 -1
  121. package/lib/packageVersion.js.map +1 -1
  122. package/lib/protocol.d.ts.map +1 -1
  123. package/lib/protocol.js +2 -1
  124. package/lib/protocol.js.map +1 -1
  125. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  126. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  127. package/lib/quorum.d.ts.map +1 -1
  128. package/lib/quorum.js.map +1 -1
  129. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  130. package/lib/retriableDocumentStorageService.js +6 -2
  131. package/lib/retriableDocumentStorageService.js.map +1 -1
  132. package/lib/utils.d.ts.map +1 -1
  133. package/lib/utils.js +6 -4
  134. package/lib/utils.js.map +1 -1
  135. package/package.json +22 -19
  136. package/prettier.config.cjs +1 -1
  137. package/src/audience.ts +52 -42
  138. package/src/catchUpMonitor.ts +39 -37
  139. package/src/collabWindowTracker.ts +75 -70
  140. package/src/connectionManager.ts +1009 -938
  141. package/src/connectionState.ts +19 -19
  142. package/src/connectionStateHandler.ts +544 -462
  143. package/src/container.ts +2040 -1785
  144. package/src/containerContext.ts +352 -337
  145. package/src/containerStorageAdapter.ts +163 -153
  146. package/src/contracts.ts +155 -145
  147. package/src/deltaManager.ts +1069 -945
  148. package/src/deltaManagerProxy.ts +143 -137
  149. package/src/deltaQueue.ts +155 -151
  150. package/src/index.ts +14 -17
  151. package/src/loader.ts +427 -422
  152. package/src/packageVersion.ts +1 -1
  153. package/src/protocol.ts +93 -87
  154. package/src/protocolTreeDocumentStorageService.ts +30 -33
  155. package/src/quorum.ts +34 -34
  156. package/src/retriableDocumentStorageService.ts +118 -102
  157. package/src/utils.ts +89 -82
  158. package/tsconfig.esnext.json +6 -6
  159. 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
- || 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
@@ -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 > 1000) {
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 ? reason : undefined /* disconnectedReason */);
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. 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.
215
237
  this._deltaManager.logConnectionIssue(Object.assign({ eventName,
216
- 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) })));
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(() => { this.lastVisible = undefined; }, 0);
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().then(() => {
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
- }).catch((error) => {
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.load(version, mode, pendingLocalState)
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" || this._lifecycleState === "closed");
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() { return this._deltaManager.connectionManager.connectionMode; }
380
- get IFluidRouter() { return this; }
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() { return this.loader.services.documentServiceFactory; }
463
- get urlResolver() { return this.loader.services.urlResolver; }
464
- get scope() { return this.loader.services.scope; }
465
- 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
+ }
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 && this.loader.services.detachedBlobStorage.size > 0) {
544
- 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
+ };
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
- && this.loader.services.detachedBlobStorage.size > 0;
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.getBlobIds().filter((id) => !redirectTable.has(id));
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({ fetchOpsFromStorage: false, reason: "createDetached" });
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.propose("code", codeDetails)
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
- 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);
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 = { reason: "DocumentOpen", mode: "write", fetchOpsFromStorage: false };
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) => 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
+ });
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 ReportIfTooLong(this.mc.logger, "WaitOps", async () => { await opsBeforeReturnP; return {}; });
837
- 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());
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 && 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" */);
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] = await Promise.all([
940
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
941
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
942
- (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
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
- 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);
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 = [client.details.environment, ` loaderVersion:${packageVersion_1.pkgVersion}`].join(";");
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, "mismatch");
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.connectionStateHandler.receivedDisconnectEvent(reason);
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 = time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
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({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
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
- 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);
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({ eventName: "NoVersionFoundWhenSpecified", id: specifiedVersion });
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) {