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