@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191457

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 (173) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +10 -6
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +5 -3
  6. package/dist/audience.js.map +1 -1
  7. package/dist/catchUpMonitor.js +2 -2
  8. package/dist/catchUpMonitor.js.map +1 -1
  9. package/dist/connectionManager.d.ts +5 -5
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +97 -93
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +15 -14
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +50 -52
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +20 -9
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +327 -277
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +2 -7
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js +2 -14
  24. package/dist/containerContext.js.map +1 -1
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +12 -13
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +21 -8
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js +3 -3
  31. package/dist/contracts.js.map +1 -1
  32. package/dist/debugLogger.d.ts +30 -0
  33. package/dist/debugLogger.d.ts.map +1 -0
  34. package/dist/debugLogger.js +95 -0
  35. package/dist/debugLogger.js.map +1 -0
  36. package/dist/deltaManager.d.ts +21 -10
  37. package/dist/deltaManager.d.ts.map +1 -1
  38. package/dist/deltaManager.js +114 -66
  39. package/dist/deltaManager.js.map +1 -1
  40. package/dist/deltaQueue.d.ts +1 -1
  41. package/dist/deltaQueue.d.ts.map +1 -1
  42. package/dist/deltaQueue.js +10 -10
  43. package/dist/deltaQueue.js.map +1 -1
  44. package/dist/disposal.d.ts +2 -2
  45. package/dist/disposal.d.ts.map +1 -1
  46. package/dist/disposal.js +1 -1
  47. package/dist/disposal.js.map +1 -1
  48. package/dist/error.d.ts +23 -0
  49. package/dist/error.d.ts.map +1 -0
  50. package/dist/error.js +32 -0
  51. package/dist/error.js.map +1 -0
  52. package/dist/loader.d.ts +22 -3
  53. package/dist/loader.d.ts.map +1 -1
  54. package/dist/loader.js +82 -51
  55. package/dist/loader.js.map +1 -1
  56. package/dist/noopHeuristic.d.ts +2 -2
  57. package/dist/noopHeuristic.d.ts.map +1 -1
  58. package/dist/noopHeuristic.js +6 -5
  59. package/dist/noopHeuristic.js.map +1 -1
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/protocol.d.ts +4 -2
  64. package/dist/protocol.d.ts.map +1 -1
  65. package/dist/protocol.js +25 -4
  66. package/dist/protocol.js.map +1 -1
  67. package/dist/quorum.d.ts +4 -1
  68. package/dist/quorum.d.ts.map +1 -1
  69. package/dist/quorum.js +1 -13
  70. package/dist/quorum.js.map +1 -1
  71. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  72. package/dist/retriableDocumentStorageService.js +4 -4
  73. package/dist/retriableDocumentStorageService.js.map +1 -1
  74. package/dist/utils.d.ts +8 -1
  75. package/dist/utils.d.ts.map +1 -1
  76. package/dist/utils.js +30 -11
  77. package/dist/utils.js.map +1 -1
  78. package/lib/audience.d.ts +1 -0
  79. package/lib/audience.d.ts.map +1 -1
  80. package/lib/audience.js +4 -2
  81. package/lib/audience.js.map +1 -1
  82. package/lib/catchUpMonitor.js +1 -1
  83. package/lib/catchUpMonitor.js.map +1 -1
  84. package/lib/connectionManager.d.ts +5 -5
  85. package/lib/connectionManager.d.ts.map +1 -1
  86. package/lib/connectionManager.js +74 -67
  87. package/lib/connectionManager.js.map +1 -1
  88. package/lib/connectionStateHandler.d.ts +15 -14
  89. package/lib/connectionStateHandler.d.ts.map +1 -1
  90. package/lib/connectionStateHandler.js +27 -29
  91. package/lib/connectionStateHandler.js.map +1 -1
  92. package/lib/container.d.ts +20 -9
  93. package/lib/container.d.ts.map +1 -1
  94. package/lib/container.js +288 -238
  95. package/lib/container.js.map +1 -1
  96. package/lib/containerContext.d.ts +2 -7
  97. package/lib/containerContext.d.ts.map +1 -1
  98. package/lib/containerContext.js +2 -14
  99. package/lib/containerContext.js.map +1 -1
  100. package/lib/containerStorageAdapter.d.ts.map +1 -1
  101. package/lib/containerStorageAdapter.js +5 -6
  102. package/lib/containerStorageAdapter.js.map +1 -1
  103. package/lib/contracts.d.ts +21 -8
  104. package/lib/contracts.d.ts.map +1 -1
  105. package/lib/contracts.js +3 -3
  106. package/lib/contracts.js.map +1 -1
  107. package/lib/debugLogger.d.ts +30 -0
  108. package/lib/debugLogger.d.ts.map +1 -0
  109. package/lib/debugLogger.js +91 -0
  110. package/lib/debugLogger.js.map +1 -0
  111. package/lib/deltaManager.d.ts +21 -10
  112. package/lib/deltaManager.d.ts.map +1 -1
  113. package/lib/deltaManager.js +88 -37
  114. package/lib/deltaManager.js.map +1 -1
  115. package/lib/deltaQueue.d.ts +1 -1
  116. package/lib/deltaQueue.d.ts.map +1 -1
  117. package/lib/deltaQueue.js +3 -3
  118. package/lib/deltaQueue.js.map +1 -1
  119. package/lib/disposal.d.ts +2 -2
  120. package/lib/disposal.d.ts.map +1 -1
  121. package/lib/disposal.js +1 -1
  122. package/lib/disposal.js.map +1 -1
  123. package/lib/error.d.ts +23 -0
  124. package/lib/error.d.ts.map +1 -0
  125. package/lib/error.js +28 -0
  126. package/lib/error.js.map +1 -0
  127. package/lib/loader.d.ts +22 -3
  128. package/lib/loader.d.ts.map +1 -1
  129. package/lib/loader.js +82 -51
  130. package/lib/loader.js.map +1 -1
  131. package/lib/noopHeuristic.d.ts +2 -2
  132. package/lib/noopHeuristic.d.ts.map +1 -1
  133. package/lib/noopHeuristic.js +2 -1
  134. package/lib/noopHeuristic.js.map +1 -1
  135. package/lib/packageVersion.d.ts +1 -1
  136. package/lib/packageVersion.js +1 -1
  137. package/lib/packageVersion.js.map +1 -1
  138. package/lib/protocol.d.ts +4 -2
  139. package/lib/protocol.d.ts.map +1 -1
  140. package/lib/protocol.js +25 -4
  141. package/lib/protocol.js.map +1 -1
  142. package/lib/quorum.d.ts +4 -1
  143. package/lib/quorum.d.ts.map +1 -1
  144. package/lib/quorum.js +0 -11
  145. package/lib/quorum.js.map +1 -1
  146. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  147. package/lib/retriableDocumentStorageService.js +2 -2
  148. package/lib/retriableDocumentStorageService.js.map +1 -1
  149. package/lib/utils.d.ts +8 -1
  150. package/lib/utils.d.ts.map +1 -1
  151. package/lib/utils.js +25 -7
  152. package/lib/utils.js.map +1 -1
  153. package/package.json +26 -32
  154. package/src/audience.ts +7 -1
  155. package/src/catchUpMonitor.ts +1 -1
  156. package/src/connectionManager.ts +75 -51
  157. package/src/connectionStateHandler.ts +31 -38
  158. package/src/container.ts +335 -240
  159. package/src/containerContext.ts +0 -16
  160. package/src/containerStorageAdapter.ts +2 -1
  161. package/src/contracts.ts +27 -11
  162. package/src/debugLogger.ts +113 -0
  163. package/src/deltaManager.ts +84 -34
  164. package/src/deltaQueue.ts +2 -1
  165. package/src/disposal.ts +2 -2
  166. package/src/error.ts +44 -0
  167. package/src/loader.ts +83 -35
  168. package/src/noopHeuristic.ts +3 -2
  169. package/src/packageVersion.ts +1 -1
  170. package/src/protocol.ts +33 -2
  171. package/src/quorum.ts +0 -10
  172. package/src/retriableDocumentStorageService.ts +2 -4
  173. package/src/utils.ts +33 -8
package/src/container.ts CHANGED
@@ -7,62 +7,54 @@
7
7
  import merge from "lodash/merge";
8
8
 
9
9
  import { v4 as uuid } from "uuid";
10
- import { IEvent } from "@fluidframework/common-definitions";
11
- import {
12
- TypedEventEmitter,
13
- assert,
14
- performance,
15
- unreachableCase,
16
- } from "@fluidframework/common-utils";
10
+ import { assert, unreachableCase } from "@fluidframework/core-utils";
11
+ import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
17
12
  import {
13
+ IEvent,
18
14
  ITelemetryProperties,
19
15
  TelemetryEventCategory,
20
16
  IRequest,
21
17
  IResponse,
22
18
  IFluidRouter,
23
19
  FluidObject,
20
+ LogLevel,
24
21
  } from "@fluidframework/core-interfaces";
25
22
  import {
23
+ AttachState,
24
+ ContainerWarning,
26
25
  IAudience,
27
- IConnectionDetailsInternal,
26
+ IBatchMessage,
27
+ ICodeDetailsLoader,
28
28
  IContainer,
29
29
  IContainerEvents,
30
- IDeltaManager,
31
- ICriticalContainerError,
32
- ContainerWarning,
33
- AttachState,
34
- IThrottlingWarning,
35
- ReadOnlyInfo,
36
30
  IContainerLoadMode,
31
+ ICriticalContainerError,
32
+ IDeltaManager,
37
33
  IFluidCodeDetails,
38
- isFluidCodeDetails,
39
- IBatchMessage,
40
- ICodeDetailsLoader,
41
34
  IHostLoader,
42
35
  IFluidModuleWithDetails,
43
36
  IProvideRuntimeFactory,
44
37
  IProvideFluidCodeDetailsComparer,
45
38
  IFluidCodeDetailsComparer,
46
39
  IRuntime,
40
+ ReadOnlyInfo,
41
+ isFluidCodeDetails,
47
42
  } from "@fluidframework/container-definitions";
48
- import { GenericError, UsageError } from "@fluidframework/container-utils";
49
43
  import {
50
- IAnyDriverError,
51
44
  IDocumentService,
52
45
  IDocumentServiceFactory,
53
46
  IDocumentStorageService,
54
47
  IResolvedUrl,
48
+ IThrottlingWarning,
55
49
  IUrlResolver,
56
50
  } from "@fluidframework/driver-definitions";
57
51
  import {
58
52
  readAndParse,
59
53
  OnlineStatus,
60
54
  isOnline,
61
- combineAppAndProtocolSummary,
62
55
  runWithRetry,
63
56
  isCombinedAppAndProtocolSummary,
64
57
  MessageType2,
65
- canBeCoalescedByService,
66
58
  } from "@fluidframework/driver-utils";
67
59
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
68
60
  import {
@@ -85,21 +77,29 @@ import {
85
77
  SummaryType,
86
78
  } from "@fluidframework/protocol-definitions";
87
79
  import {
88
- ChildLogger,
80
+ createChildLogger,
89
81
  EventEmitterWithErrorHandling,
90
82
  PerformanceEvent,
91
83
  raiseConnectedEvent,
92
- TelemetryLogger,
93
84
  connectedEventName,
94
85
  normalizeError,
95
86
  MonitoringContext,
96
- loggerToMonitoringContext,
87
+ createChildMonitoringContext,
97
88
  wrapError,
98
89
  ITelemetryLoggerExt,
90
+ formatTick,
91
+ GenericError,
92
+ UsageError,
99
93
  } from "@fluidframework/telemetry-utils";
100
94
  import { Audience } from "./audience";
101
95
  import { ContainerContext } from "./containerContext";
102
- import { ReconnectMode, IConnectionManagerFactoryArgs, getPackageName } from "./contracts";
96
+ import {
97
+ ReconnectMode,
98
+ IConnectionManagerFactoryArgs,
99
+ getPackageName,
100
+ IConnectionDetailsInternal,
101
+ IConnectionStateChangeReason,
102
+ } from "./contracts";
103
103
  import { DeltaManager, IConnectionArgs } from "./deltaManager";
104
104
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
105
105
  import { pkgVersion } from "./packageVersion";
@@ -110,8 +110,12 @@ import {
110
110
  ISerializableBlobContents,
111
111
  } from "./containerStorageAdapter";
112
112
  import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
113
- import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
114
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
113
+ import {
114
+ combineAppAndProtocolSummary,
115
+ getProtocolSnapshotTree,
116
+ getSnapshotTreeFromSerializedContainer,
117
+ } from "./utils";
118
+ import { initQuorumValuesFromCodeDetails } from "./quorum";
115
119
  import { NoopHeuristic } from "./noopHeuristic";
116
120
  import { ConnectionManager } from "./connectionManager";
117
121
  import { ConnectionState } from "./connectionState";
@@ -151,6 +155,11 @@ export interface IContainerLoadProps {
151
155
  * The pending state serialized from a pervious container instance
152
156
  */
153
157
  readonly pendingLocalState?: IPendingContainerState;
158
+
159
+ /**
160
+ * Load the container to at least this sequence number.
161
+ */
162
+ readonly loadToSequenceNumber?: number;
154
163
  }
155
164
 
156
165
  /**
@@ -206,6 +215,10 @@ export interface IContainerCreateProps {
206
215
  */
207
216
  readonly detachedBlobStorage?: IDetachedBlobStorage;
208
217
 
218
+ /**
219
+ * Optional property for allowing the container to use a custom
220
+ * protocol implementation for handling the quorum and/or the audience.
221
+ */
209
222
  readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
210
223
  }
211
224
 
@@ -369,7 +382,8 @@ export class Container
369
382
  loadProps: IContainerLoadProps,
370
383
  createProps: IContainerCreateProps,
371
384
  ): Promise<Container> {
372
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
385
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
386
+ loadProps;
373
387
 
374
388
  const container = new Container(createProps, loadProps);
375
389
 
@@ -398,7 +412,7 @@ export class Container
398
412
  container.on("closed", onClosed);
399
413
 
400
414
  container
401
- .load(version, mode, resolvedUrl, pendingLocalState)
415
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
402
416
  .finally(() => {
403
417
  container.removeListener("closed", onClosed);
404
418
  })
@@ -412,7 +426,11 @@ export class Container
412
426
  // Depending where error happens, we can be attempting to connect to web socket
413
427
  // and continuously retrying (consider offline mode)
414
428
  // Host has no container to close, so it's prudent to do it here
429
+ // Note: We could only dispose the container instead of just close but that would
430
+ // the telemetry where users sometimes search for ContainerClose event to look
431
+ // for load failures. So not removing this at this time.
415
432
  container.close(err);
433
+ container.dispose(err);
416
434
  onClosed(err);
417
435
  },
418
436
  );
@@ -473,7 +491,7 @@ export class Container
473
491
  private readonly codeLoader: ICodeDetailsLoader;
474
492
  private readonly options: ILoaderOptions;
475
493
  private readonly scope: FluidObject;
476
- private readonly subLogger: TelemetryLogger;
494
+ private readonly subLogger: ITelemetryLoggerExt;
477
495
  private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
478
496
  private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
479
497
 
@@ -523,13 +541,14 @@ export class Container
523
541
 
524
542
  public get closed(): boolean {
525
543
  return (
526
- this._lifecycleState === "closing" ||
527
- this._lifecycleState === "closed" ||
528
- this._lifecycleState === "disposing" ||
529
- this._lifecycleState === "disposed"
544
+ this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed
530
545
  );
531
546
  }
532
547
 
548
+ public get disposed(): boolean {
549
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
550
+ }
551
+
533
552
  private _attachState = AttachState.Detached;
534
553
 
535
554
  private readonly storageAdapter: ContainerStorageAdapter;
@@ -556,7 +575,6 @@ export class Container
556
575
  private inboundQueuePausedFromInit = true;
557
576
  private firstConnection = true;
558
577
  private readonly connectionTransitionTimes: number[] = [];
559
- private messageCountAfterDisconnection: number = 0;
560
578
  private _loadedFromVersion: IVersion | undefined;
561
579
  private attachStarted = false;
562
580
  private _dirtyContainer = false;
@@ -735,6 +753,7 @@ export class Container
735
753
 
736
754
  this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
737
755
  const pendingLocalState = loadProps?.pendingLocalState;
756
+ this._clientId = pendingLocalState?.clientId;
738
757
 
739
758
  this._canReconnect = canReconnect ?? true;
740
759
  this.clientDetailsOverride = clientDetailsOverride;
@@ -748,7 +767,19 @@ export class Container
748
767
  this.scope = scope;
749
768
  this.detachedBlobStorage = detachedBlobStorage;
750
769
  this.protocolHandlerBuilder =
751
- protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
770
+ protocolHandlerBuilder ??
771
+ ((
772
+ attributes: IDocumentAttributes,
773
+ quorumSnapshot: IQuorumSnapshot,
774
+ sendProposal: (key: string, value: any) => number,
775
+ ) =>
776
+ new ProtocolHandler(
777
+ attributes,
778
+ quorumSnapshot,
779
+ sendProposal,
780
+ new Audience(),
781
+ (clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
782
+ ));
752
783
 
753
784
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
754
785
  this.clone = async (
@@ -769,53 +800,56 @@ export class Container
769
800
  }`;
770
801
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
771
802
  // We assign the id later so property getter is used.
772
- this.subLogger = ChildLogger.create(subLogger, undefined, {
773
- all: {
774
- clientType, // Differentiating summarizer container from main container
775
- containerId: uuid(),
776
- docId: () => this.resolvedUrl?.id,
777
- containerAttachState: () => this._attachState,
778
- containerLifecycleState: () => this._lifecycleState,
779
- containerConnectionState: () => ConnectionState[this.connectionState],
780
- serializedContainer: pendingLocalState !== undefined,
781
- },
782
- // we need to be judicious with our logging here to avoid generating too much data
783
- // all data logged here should be broadly applicable, and not specific to a
784
- // specific error or class of errors
785
- error: {
786
- // load information to associate errors with the specific load point
787
- dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
788
- dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
789
- dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
790
- containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
791
- containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
792
- // message information to associate errors with the specific execution state
793
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
794
- dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
795
- dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
796
- dmLastMsqSeqClientId: () =>
797
- this.deltaManager?.lastMessage?.clientId === null
798
- ? "null"
799
- : this.deltaManager?.lastMessage?.clientId,
800
- dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
801
- connectionStateDuration: () =>
802
- performance.now() - this.connectionTransitionTimes[this.connectionState],
803
+ this.subLogger = createChildLogger({
804
+ logger: subLogger,
805
+ properties: {
806
+ all: {
807
+ clientType, // Differentiating summarizer container from main container
808
+ containerId: uuid(),
809
+ docId: () => this.resolvedUrl?.id,
810
+ containerAttachState: () => this._attachState,
811
+ containerLifecycleState: () => this._lifecycleState,
812
+ containerConnectionState: () => ConnectionState[this.connectionState],
813
+ serializedContainer: pendingLocalState !== undefined,
814
+ },
815
+ // we need to be judicious with our logging here to avoid generating too much data
816
+ // all data logged here should be broadly applicable, and not specific to a
817
+ // specific error or class of errors
818
+ error: {
819
+ // load information to associate errors with the specific load point
820
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
821
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
822
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
823
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
824
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
825
+ // message information to associate errors with the specific execution state
826
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
827
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
828
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
829
+ dmLastMsqSeqClientId: () =>
830
+ this.deltaManager?.lastMessage?.clientId === null
831
+ ? "null"
832
+ : this.deltaManager?.lastMessage?.clientId,
833
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
834
+ connectionStateDuration: () =>
835
+ performance.now() - this.connectionTransitionTimes[this.connectionState],
836
+ },
803
837
  },
804
838
  });
805
839
 
806
840
  // Prefix all events in this file with container-loader
807
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
841
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
808
842
 
809
843
  this._deltaManager = this.createDeltaManager();
810
844
 
811
845
  this.connectionStateHandler = createConnectionStateHandler(
812
846
  {
813
847
  logger: this.mc.logger,
814
- connectionStateChanged: (value, oldState, reason, error) => {
848
+ connectionStateChanged: (value, oldState, reason) => {
815
849
  if (value === ConnectionState.Connected) {
816
850
  this._clientId = this.connectionStateHandler.pendingClientId;
817
851
  }
818
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
852
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
819
853
  if (this._lifecycleState === "loaded") {
820
854
  this.propagateConnectionState(
821
855
  false /* initial transition */,
@@ -857,8 +891,9 @@ export class Container
857
891
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
858
892
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
859
893
  if (mode === "read") {
860
- this.disconnect();
861
- this.connect();
894
+ const reason = { text: "NoJoinSignal" };
895
+ this.disconnectInternal(reason);
896
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
862
897
  }
863
898
  },
864
899
  clientShouldHaveLeft: (clientId: string) => {
@@ -900,8 +935,8 @@ export class Container
900
935
  document !== null &&
901
936
  typeof document.addEventListener === "function" &&
902
937
  document.addEventListener !== null;
903
- // keep track of last time page was visible for telemetry
904
- if (isDomAvailable) {
938
+ // keep track of last time page was visible for telemetry (on interactive clients only)
939
+ if (isDomAvailable && interactive) {
905
940
  this.lastVisible = document.hidden ? performance.now() : undefined;
906
941
  this.visibilityEventHandler = () => {
907
942
  if (document.hidden) {
@@ -1051,47 +1086,65 @@ export class Container
1051
1086
  }
1052
1087
  }
1053
1088
 
1054
- public closeAndGetPendingLocalState(): string {
1089
+ public async closeAndGetPendingLocalState(): Promise<string> {
1055
1090
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
1056
1091
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
1057
1092
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1058
- const pendingState = this.getPendingLocalState();
1093
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1094
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
1059
1095
  this.close();
1060
1096
  return pendingState;
1061
1097
  }
1062
1098
 
1063
- public getPendingLocalState(): string {
1064
- if (!this.offlineLoadEnabled) {
1065
- throw new UsageError("Can't get pending local state unless offline load is enabled");
1066
- }
1067
- if (this.closed || this._disposed) {
1068
- throw new UsageError(
1069
- "Pending state cannot be retried if the container is closed or disposed",
1070
- );
1071
- }
1072
- assert(
1073
- this.attachState === AttachState.Attached,
1074
- 0x0d1 /* "Container should be attached before close" */,
1075
- );
1076
- assert(
1077
- this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
1078
- 0x0d2 /* "resolved url should be valid Fluid url" */,
1079
- );
1080
- assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1081
- assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1082
- const pendingState: IPendingContainerState = {
1083
- pendingRuntimeState: this.runtime.getPendingLocalState(),
1084
- baseSnapshot: this.baseSnapshot,
1085
- snapshotBlobs: this.baseSnapshotBlobs,
1086
- savedOps: this.savedOps,
1087
- url: this.resolvedUrl.url,
1088
- term: OnlyValidTermValue,
1089
- clientId: this.clientId,
1090
- };
1091
-
1092
- this.mc.logger.sendTelemetryEvent({ eventName: "GetPendingLocalState" });
1099
+ public async getPendingLocalState(): Promise<string> {
1100
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
1101
+ }
1093
1102
 
1094
- return JSON.stringify(pendingState);
1103
+ private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) {
1104
+ return PerformanceEvent.timedExecAsync(
1105
+ this.mc.logger,
1106
+ {
1107
+ eventName: "getPendingLocalState",
1108
+ notifyImminentClosure: props.notifyImminentClosure,
1109
+ savedOpsSize: this.savedOps.length,
1110
+ clientId: this.clientId,
1111
+ },
1112
+ async () => {
1113
+ if (!this.offlineLoadEnabled) {
1114
+ throw new UsageError(
1115
+ "Can't get pending local state unless offline load is enabled",
1116
+ );
1117
+ }
1118
+ if (this.closed || this._disposed) {
1119
+ throw new UsageError(
1120
+ "Pending state cannot be retried if the container is closed or disposed",
1121
+ );
1122
+ }
1123
+ assert(
1124
+ this.attachState === AttachState.Attached,
1125
+ 0x0d1 /* "Container should be attached before close" */,
1126
+ );
1127
+ assert(
1128
+ this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
1129
+ 0x0d2 /* "resolved url should be valid Fluid url" */,
1130
+ );
1131
+ assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1132
+ assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1133
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
1134
+ const pendingState: IPendingContainerState = {
1135
+ pendingRuntimeState,
1136
+ baseSnapshot: this.baseSnapshot,
1137
+ snapshotBlobs: this.baseSnapshotBlobs,
1138
+ savedOps: this.savedOps,
1139
+ url: this.resolvedUrl.url,
1140
+ term: OnlyValidTermValue,
1141
+ // no need to save this if there is no pending runtime state
1142
+ clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
1143
+ };
1144
+
1145
+ return JSON.stringify(pendingState);
1146
+ },
1147
+ );
1095
1148
  }
1096
1149
 
1097
1150
  public get attachState(): AttachState {
@@ -1117,7 +1170,10 @@ export class Container
1117
1170
  return JSON.stringify(combinedSummary);
1118
1171
  }
1119
1172
 
1120
- public async attach(request: IRequest): Promise<void> {
1173
+ public async attach(
1174
+ request: IRequest,
1175
+ attachProps?: { deltaConnection?: "none" | "delayed" },
1176
+ ): Promise<void> {
1121
1177
  await PerformanceEvent.timedExecAsync(
1122
1178
  this.mc.logger,
1123
1179
  { eventName: "Attach" },
@@ -1243,10 +1299,13 @@ export class Container
1243
1299
  this.emit("attached");
1244
1300
 
1245
1301
  if (!this.closed) {
1246
- this.resumeInternal({
1247
- fetchOpsFromStorage: false,
1248
- reason: "createDetached",
1249
- });
1302
+ this.handleDeltaConnectionArg(
1303
+ {
1304
+ fetchOpsFromStorage: false,
1305
+ reason: { text: "createDetached" },
1306
+ },
1307
+ attachProps?.deltaConnection,
1308
+ );
1250
1309
  }
1251
1310
  } catch (error) {
1252
1311
  // add resolved URL on error object so that host has the ability to find this document and delete it
@@ -1269,7 +1328,7 @@ export class Container
1269
1328
  );
1270
1329
  }
1271
1330
 
1272
- private setAutoReconnectInternal(mode: ReconnectMode) {
1331
+ private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1273
1332
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
1274
1333
 
1275
1334
  if (currentMode === mode) {
@@ -1288,7 +1347,7 @@ export class Container
1288
1347
  duration,
1289
1348
  });
1290
1349
 
1291
- this._deltaManager.connectionManager.setAutoReconnect(mode);
1350
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
1292
1351
  }
1293
1352
 
1294
1353
  public connect() {
@@ -1300,7 +1359,10 @@ export class Container
1300
1359
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
1301
1360
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
1302
1361
  // assuming that connect() is called quickly after initial container boot.
1303
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
1362
+ this.connectInternal({
1363
+ reason: { text: "DocumentConnect" },
1364
+ fetchOpsFromStorage: false,
1365
+ });
1304
1366
  }
1305
1367
  }
1306
1368
 
@@ -1316,23 +1378,23 @@ export class Container
1316
1378
 
1317
1379
  // Set Auto Reconnect Mode
1318
1380
  const mode = ReconnectMode.Enabled;
1319
- this.setAutoReconnectInternal(mode);
1381
+ this.setAutoReconnectInternal(mode, args.reason);
1320
1382
  }
1321
1383
 
1322
1384
  public disconnect() {
1323
1385
  if (this.closed) {
1324
1386
  throw new UsageError(`The Container is closed and cannot be disconnected`);
1325
1387
  } else {
1326
- this.disconnectInternal();
1388
+ this.disconnectInternal({ text: "DocumentDisconnect" });
1327
1389
  }
1328
1390
  }
1329
1391
 
1330
- private disconnectInternal() {
1392
+ private disconnectInternal(reason: IConnectionStateChangeReason) {
1331
1393
  assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
1332
1394
 
1333
1395
  // Set Auto Reconnect Mode
1334
1396
  const mode = ReconnectMode.Disabled;
1335
- this.setAutoReconnectInternal(mode);
1397
+ this.setAutoReconnectInternal(mode, reason);
1336
1398
  }
1337
1399
 
1338
1400
  private resumeInternal(args: IConnectionArgs) {
@@ -1465,8 +1527,10 @@ export class Container
1465
1527
  specifiedVersion: string | undefined,
1466
1528
  loadMode: IContainerLoadMode,
1467
1529
  resolvedUrl: IResolvedUrl,
1468
- pendingLocalState?: IPendingContainerState,
1530
+ pendingLocalState: IPendingContainerState | undefined,
1531
+ loadToSequenceNumber: number | undefined,
1469
1532
  ) {
1533
+ const timings: Record<string, number> = { phase1: performance.now() };
1470
1534
  this.service = await this.serviceFactory.createDocumentService(
1471
1535
  resolvedUrl,
1472
1536
  this.subLogger,
@@ -1483,7 +1547,7 @@ export class Container
1483
1547
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1484
1548
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1485
1549
  const connectionArgs: IConnectionArgs = {
1486
- reason: "DocumentOpen",
1550
+ reason: { text: "DocumentOpen" },
1487
1551
  mode: "write",
1488
1552
  fetchOpsFromStorage: false,
1489
1553
  };
@@ -1505,6 +1569,7 @@ export class Container
1505
1569
 
1506
1570
  this._attachState = AttachState.Attached;
1507
1571
 
1572
+ timings.phase2 = performance.now();
1508
1573
  // Fetch specified snapshot.
1509
1574
  const { snapshot, versionId } =
1510
1575
  pendingLocalState === undefined
@@ -1539,6 +1604,57 @@ export class Container
1539
1604
 
1540
1605
  let opsBeforeReturnP: Promise<void> | undefined;
1541
1606
 
1607
+ if (loadMode.pauseAfterLoad === true) {
1608
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
1609
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
1610
+ assert(
1611
+ loadToSequenceNumber !== undefined,
1612
+ 0x727 /* sequenceNumber should be defined */,
1613
+ );
1614
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
1615
+ // due to saved ops that may be replayed after the snapshot.
1616
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
1617
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
1618
+ throw new Error(
1619
+ "Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
1620
+ );
1621
+ }
1622
+ }
1623
+
1624
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
1625
+ this.forceReadonly(true);
1626
+
1627
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
1628
+ const opHandler = () => {
1629
+ if (loadToSequenceNumber === undefined) {
1630
+ // If there is no specified sequence number, pause after the inbound queue is empty.
1631
+ if (this.deltaManager.inbound.length !== 0) {
1632
+ return;
1633
+ }
1634
+ } else {
1635
+ // If there is a specified sequence number, keep processing until we reach it.
1636
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1637
+ return;
1638
+ }
1639
+ }
1640
+
1641
+ // Pause op processing once we have processed the desired number of ops.
1642
+ void this.deltaManager.inbound.pause();
1643
+ void this.deltaManager.outbound.pause();
1644
+ this.off("op", opHandler);
1645
+ };
1646
+ if (
1647
+ (loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
1648
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber
1649
+ ) {
1650
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
1651
+ opHandler();
1652
+ } else {
1653
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
1654
+ this.on("op", opHandler);
1655
+ }
1656
+ }
1657
+
1542
1658
  // Attach op handlers to finish initialization and be able to start processing ops
1543
1659
  // Kick off any ops fetching if required.
1544
1660
  switch (loadMode.opsBeforeReturn) {
@@ -1550,11 +1666,13 @@ export class Container
1550
1666
  loadMode.deltaConnection !== "none" ? "all" : "none",
1551
1667
  );
1552
1668
  break;
1669
+ case "sequenceNumber":
1553
1670
  case "cached":
1554
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
1555
- break;
1556
1671
  case "all":
1557
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
1672
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(
1673
+ dmAttributes,
1674
+ loadMode.opsBeforeReturn,
1675
+ );
1558
1676
  break;
1559
1677
  default:
1560
1678
  unreachableCase(loadMode.opsBeforeReturn);
@@ -1564,12 +1682,13 @@ export class Container
1564
1682
  // Initialize the protocol handler
1565
1683
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1566
1684
 
1685
+ timings.phase3 = performance.now();
1567
1686
  const codeDetails = this.getCodeDetailsFromQuorum();
1568
- await this.instantiateContext(
1569
- true, // existing
1687
+ await this.instantiateRuntime(
1570
1688
  codeDetails,
1571
1689
  snapshot,
1572
- pendingLocalState?.pendingRuntimeState,
1690
+ // give runtime a dummy value so it knows we're loading from a stash blob
1691
+ pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
1573
1692
  );
1574
1693
 
1575
1694
  // replay saved ops
@@ -1581,13 +1700,6 @@ export class Container
1581
1700
  await this.runtime.notifyOpReplay?.(message);
1582
1701
  }
1583
1702
  pendingLocalState.savedOps = [];
1584
-
1585
- // now set clientId to stashed clientId so live ops are correctly processed as local
1586
- assert(
1587
- this.clientId === undefined,
1588
- 0x5d6 /* Unexpected clientId when setting stashed clientId */,
1589
- );
1590
- this._clientId = pendingLocalState?.clientId;
1591
1703
  }
1592
1704
 
1593
1705
  // We might have hit some failure that did not manifest itself in exception in this flow,
@@ -1611,27 +1723,27 @@ export class Container
1611
1723
  this._deltaManager.inbound.pause();
1612
1724
  }
1613
1725
 
1614
- switch (loadMode.deltaConnection) {
1615
- case undefined:
1616
- if (pendingLocalState) {
1617
- // connect to delta stream now since we did not before
1618
- this.connectToDeltaStream(connectionArgs);
1726
+ this.handleDeltaConnectionArg(
1727
+ connectionArgs,
1728
+ loadMode.deltaConnection,
1729
+ pendingLocalState !== undefined,
1730
+ );
1731
+ }
1732
+
1733
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1734
+ if (
1735
+ loadToSequenceNumber !== undefined &&
1736
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber
1737
+ ) {
1738
+ await new Promise<void>((resolve, reject) => {
1739
+ const opHandler = (message: ISequencedDocumentMessage) => {
1740
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1741
+ resolve();
1742
+ this.off("op", opHandler);
1619
1743
  }
1620
- // intentional fallthrough
1621
- case "delayed":
1622
- assert(
1623
- this.inboundQueuePausedFromInit,
1624
- 0x346 /* inboundQueuePausedFromInit should be true */,
1625
- );
1626
- this.inboundQueuePausedFromInit = false;
1627
- this._deltaManager.inbound.resume();
1628
- this._deltaManager.inboundSignal.resume();
1629
- break;
1630
- case "none":
1631
- break;
1632
- default:
1633
- unreachableCase(loadMode.deltaConnection);
1634
- }
1744
+ };
1745
+ this.on("op", opHandler);
1746
+ });
1635
1747
  }
1636
1748
 
1637
1749
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
@@ -1645,7 +1757,15 @@ export class Container
1645
1757
 
1646
1758
  // Internal context is fully loaded at this point
1647
1759
  this.setLoaded();
1648
-
1760
+ timings.end = performance.now();
1761
+ this.subLogger.sendTelemetryEvent(
1762
+ {
1763
+ eventName: "LoadStagesTimings",
1764
+ details: JSON.stringify(timings),
1765
+ },
1766
+ undefined,
1767
+ LogLevel.verbose,
1768
+ );
1649
1769
  return {
1650
1770
  sequenceNumber: attributes.sequenceNumber,
1651
1771
  version: versionId,
@@ -1654,7 +1774,7 @@ export class Container
1654
1774
  };
1655
1775
  }
1656
1776
 
1657
- private async createDetached(source: IFluidCodeDetails) {
1777
+ private async createDetached(codeDetails: IFluidCodeDetails) {
1658
1778
  const attributes: IDocumentAttributes = {
1659
1779
  sequenceNumber: detachedContainerRefSeqNumber,
1660
1780
  term: OnlyValidTermValue,
@@ -1664,7 +1784,7 @@ export class Container
1664
1784
  await this.attachDeltaManagerOpHandler(attributes);
1665
1785
 
1666
1786
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1667
- const qValues = initQuorumValuesFromCodeDetails(source);
1787
+ const qValues = initQuorumValuesFromCodeDetails(codeDetails);
1668
1788
  this.initializeProtocolState(
1669
1789
  attributes,
1670
1790
  {
@@ -1674,10 +1794,7 @@ export class Container
1674
1794
  }, // IQuorumSnapShot
1675
1795
  );
1676
1796
 
1677
- // The load context - given we seeded the quorum - will be great
1678
- await this.instantiateContextDetached(
1679
- false, // existing
1680
- );
1797
+ await this.instantiateRuntime(codeDetails, undefined);
1681
1798
 
1682
1799
  this.setLoaded();
1683
1800
  }
@@ -1703,21 +1820,17 @@ export class Container
1703
1820
  this.storageAdapter,
1704
1821
  baseTree.blobs.quorumValues,
1705
1822
  );
1706
- const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1707
1823
  this.initializeProtocolState(
1708
1824
  attributes,
1709
1825
  {
1710
1826
  members: [],
1711
1827
  proposals: [],
1712
- values:
1713
- codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1828
+ values: qValues,
1714
1829
  }, // IQuorumSnapShot
1715
1830
  );
1831
+ const codeDetails = this.getCodeDetailsFromQuorum();
1716
1832
 
1717
- await this.instantiateContextDetached(
1718
- true, // existing
1719
- snapshotTree,
1720
- );
1833
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1721
1834
 
1722
1835
  this.setLoaded();
1723
1836
  }
@@ -1786,7 +1899,10 @@ export class Container
1786
1899
  this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
1787
1900
  );
1788
1901
 
1789
- const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1902
+ const protocolLogger = createChildLogger({
1903
+ logger: this.subLogger,
1904
+ namespace: "ProtocolHandler",
1905
+ });
1790
1906
 
1791
1907
  protocol.quorum.on("error", (error) => {
1792
1908
  protocolLogger.sendErrorEvent(error);
@@ -1897,7 +2013,7 @@ export class Container
1897
2013
  const serviceProvider = () => this.service;
1898
2014
  const deltaManager = new DeltaManager<ConnectionManager>(
1899
2015
  serviceProvider,
1900
- ChildLogger.create(this.subLogger, "DeltaManager"),
2016
+ createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }),
1901
2017
  () => this.activeConnection(),
1902
2018
  (props: IConnectionManagerFactoryArgs) =>
1903
2019
  new ConnectionManager(
@@ -1905,7 +2021,7 @@ export class Container
1905
2021
  () => this.isDirty,
1906
2022
  this.client,
1907
2023
  this._canReconnect,
1908
- ChildLogger.create(this.subLogger, "ConnectionManager"),
2024
+ createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }),
1909
2025
  props,
1910
2026
  ),
1911
2027
  );
@@ -1921,18 +2037,18 @@ export class Container
1921
2037
  this.connectionStateHandler.receivedConnectEvent(details);
1922
2038
  });
1923
2039
 
1924
- deltaManager.on("establishingConnection", (reason: string) => {
2040
+ deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
1925
2041
  this.connectionStateHandler.establishingConnection(reason);
1926
2042
  });
1927
2043
 
1928
- deltaManager.on("cancelEstablishingConnection", (reason: string) => {
2044
+ deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
1929
2045
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1930
2046
  });
1931
2047
 
1932
- deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
2048
+ deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
1933
2049
  this.noopHeuristic?.notifyDisconnect();
1934
2050
  if (!this.closed) {
1935
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
2051
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1936
2052
  }
1937
2053
  });
1938
2054
 
@@ -1967,7 +2083,7 @@ export class Container
1967
2083
 
1968
2084
  private async attachDeltaManagerOpHandler(
1969
2085
  attributes: IDocumentAttributes,
1970
- prefetchType?: "cached" | "all" | "none",
2086
+ prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
1971
2087
  ) {
1972
2088
  return this._deltaManager.attachOpHandler(
1973
2089
  attributes.minimumSequenceNumber,
@@ -1985,8 +2101,7 @@ export class Container
1985
2101
  private logConnectionStateChangeTelemetry(
1986
2102
  value: ConnectionState,
1987
2103
  oldState: ConnectionState,
1988
- reason?: string,
1989
- error?: IAnyDriverError,
2104
+ reason?: IConnectionStateChangeReason,
1990
2105
  ) {
1991
2106
  // Log actual event
1992
2107
  const time = performance.now();
@@ -2004,7 +2119,7 @@ export class Container
2004
2119
  if (value === ConnectionState.Connected) {
2005
2120
  durationFromDisconnected =
2006
2121
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
2007
- durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
2122
+ durationFromDisconnected = formatTick(durationFromDisconnected);
2008
2123
  } else if (value === ConnectionState.CatchingUp) {
2009
2124
  // This info is of most interesting while Catching Up.
2010
2125
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
@@ -2025,7 +2140,7 @@ export class Container
2025
2140
  from: ConnectionState[oldState],
2026
2141
  duration,
2027
2142
  durationFromDisconnected,
2028
- reason,
2143
+ reason: reason?.text,
2029
2144
  connectionInitiationReason,
2030
2145
  pendingClientId: this.connectionStateHandler.pendingClientId,
2031
2146
  clientId: this.clientId,
@@ -2038,9 +2153,10 @@ export class Container
2038
2153
  : undefined,
2039
2154
  checkpointSequenceNumber,
2040
2155
  quorumSize: this._protocolHandler?.quorum.getMembers().size,
2156
+ isDirty: this.isDirty,
2041
2157
  ...this._deltaManager.connectionProps,
2042
2158
  },
2043
- error,
2159
+ reason?.error,
2044
2160
  );
2045
2161
 
2046
2162
  if (value === ConnectionState.Connected) {
@@ -2048,7 +2164,10 @@ export class Container
2048
2164
  }
2049
2165
  }
2050
2166
 
2051
- private propagateConnectionState(initialTransition: boolean, disconnectedReason?: string) {
2167
+ private propagateConnectionState(
2168
+ initialTransition: boolean,
2169
+ disconnectedReason?: IConnectionStateChangeReason,
2170
+ ) {
2052
2171
  // When container loaded, we want to propagate initial connection state.
2053
2172
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
2054
2173
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -2061,26 +2180,11 @@ export class Container
2061
2180
  }
2062
2181
  const state = this.connectionState === ConnectionState.Connected;
2063
2182
 
2064
- const logOpsOnReconnect: boolean =
2065
- this.connectionState === ConnectionState.Connected &&
2066
- !this.firstConnection &&
2067
- this.connectionMode === "write";
2068
- if (logOpsOnReconnect) {
2069
- this.messageCountAfterDisconnection = 0;
2070
- }
2071
-
2072
2183
  // Both protocol and context should not be undefined if we got so far.
2073
2184
 
2074
2185
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2075
2186
  this.protocolHandler.setConnectionState(state, this.clientId);
2076
- raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
2077
-
2078
- if (logOpsOnReconnect) {
2079
- this.mc.logger.sendTelemetryEvent({
2080
- eventName: "OpsSentOnReconnect",
2081
- count: this.messageCountAfterDisconnection,
2082
- });
2083
- }
2187
+ raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
2084
2188
  }
2085
2189
 
2086
2190
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2156,7 +2260,6 @@ export class Container
2156
2260
  return -1;
2157
2261
  }
2158
2262
 
2159
- this.messageCountAfterDisconnection += 1;
2160
2263
  this.noopHeuristic?.notifyMessageSent();
2161
2264
  return this._deltaManager.submit(
2162
2265
  type,
@@ -2174,28 +2277,6 @@ export class Container
2174
2277
  }
2175
2278
  const local = this.clientId === message.clientId;
2176
2279
 
2177
- // Check and report if we're getting messages from a clientId that we previously
2178
- // flagged should have left, or from a client that's not in the quorum but should be
2179
- if (message.clientId != null) {
2180
- const client = this.protocolHandler.quorum.getMember(message.clientId);
2181
-
2182
- if (client === undefined && message.type !== MessageType.ClientJoin) {
2183
- // pre-0.58 error message: messageClientIdMissingFromQuorum
2184
- throw new Error("Remote message's clientId is missing from the quorum");
2185
- }
2186
-
2187
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
2188
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
2189
- // document we don't need to blow up aggressively.
2190
- if (
2191
- this.clientsWhoShouldHaveLeft.has(message.clientId) &&
2192
- !canBeCoalescedByService(message)
2193
- ) {
2194
- // pre-0.58 error message: messageClientIdShouldHaveLeft
2195
- throw new Error("Remote message's clientId already should have left");
2196
- }
2197
- }
2198
-
2199
2280
  // Allow the protocol handler to process the message
2200
2281
  const result = this.protocolHandler.processMessage(message, local);
2201
2282
 
@@ -2280,17 +2361,7 @@ export class Container
2280
2361
  return { snapshot, versionId: version?.id };
2281
2362
  }
2282
2363
 
2283
- private async instantiateContextDetached(existing: boolean, snapshot?: ISnapshotTree) {
2284
- const codeDetails = this.getCodeDetailsFromQuorum();
2285
- if (codeDetails === undefined) {
2286
- throw new Error("pkg should be provided in create flow!!");
2287
- }
2288
-
2289
- await this.instantiateContext(existing, codeDetails, snapshot);
2290
- }
2291
-
2292
- private async instantiateContext(
2293
- existing: boolean,
2364
+ private async instantiateRuntime(
2294
2365
  codeDetails: IFluidCodeDetails,
2295
2366
  snapshot: ISnapshotTree | undefined,
2296
2367
  pendingLocalState?: unknown,
@@ -2327,6 +2398,8 @@ export class Container
2327
2398
  (this.protocolHandler.quorum.get("code") ??
2328
2399
  this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
2329
2400
 
2401
+ const existing = snapshot !== undefined;
2402
+
2330
2403
  const context = new ContainerContext(
2331
2404
  this.options,
2332
2405
  this.scope,
@@ -2350,7 +2423,6 @@ export class Container
2350
2423
  this.getAbsoluteUrl,
2351
2424
  () => this.resolvedUrl?.id,
2352
2425
  () => this.clientId,
2353
- () => this._deltaManager.serviceConfiguration,
2354
2426
  () => this.attachState,
2355
2427
  () => this.connected,
2356
2428
  getSpecifiedCodeDetails,
@@ -2359,9 +2431,6 @@ export class Container
2359
2431
  this.subLogger,
2360
2432
  pendingLocalState,
2361
2433
  );
2362
- this._lifecycleEvents.once("disposed", () => {
2363
- context.dispose();
2364
- });
2365
2434
 
2366
2435
  this._runtime = await PerformanceEvent.timedExecAsync(
2367
2436
  this.subLogger,
@@ -2371,8 +2440,6 @@ export class Container
2371
2440
  this._lifecycleEvents.emit("runtimeInstantiated");
2372
2441
 
2373
2442
  this._loadedCodeDetails = codeDetails;
2374
-
2375
- this.emit("contextChanged", codeDetails);
2376
2443
  }
2377
2444
 
2378
2445
  private readonly updateDirtyContainerState = (dirty: boolean) => {
@@ -2400,6 +2467,34 @@ export class Container
2400
2467
  this.runtime.setConnectionState(state && !readonly, this.clientId);
2401
2468
  }
2402
2469
  }
2470
+
2471
+ private handleDeltaConnectionArg(
2472
+ connectionArgs: IConnectionArgs,
2473
+ deltaConnectionArg?: "none" | "delayed",
2474
+ canConnect: boolean = true,
2475
+ ) {
2476
+ switch (deltaConnectionArg) {
2477
+ case undefined:
2478
+ if (canConnect) {
2479
+ // connect to delta stream now since we did not before
2480
+ this.connectToDeltaStream(connectionArgs);
2481
+ }
2482
+ // intentional fallthrough
2483
+ case "delayed":
2484
+ assert(
2485
+ this.inboundQueuePausedFromInit,
2486
+ 0x346 /* inboundQueuePausedFromInit should be true */,
2487
+ );
2488
+ this.inboundQueuePausedFromInit = false;
2489
+ this._deltaManager.inbound.resume();
2490
+ this._deltaManager.inboundSignal.resume();
2491
+ break;
2492
+ case "none":
2493
+ break;
2494
+ default:
2495
+ unreachableCase(deltaConnectionArg);
2496
+ }
2497
+ }
2403
2498
  }
2404
2499
 
2405
2500
  /**
@@ -2415,7 +2510,7 @@ export interface IContainerExperimental extends IContainer {
2415
2510
  * @experimental misuse of this API can result in duplicate op submission and potential document corruption
2416
2511
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2417
2512
  */
2418
- getPendingLocalState?(): string;
2513
+ getPendingLocalState?(): Promise<string>;
2419
2514
 
2420
2515
  /**
2421
2516
  * Closes the container and returns serialized local state intended to be
@@ -2423,5 +2518,5 @@ export interface IContainerExperimental extends IContainer {
2423
2518
  * @experimental
2424
2519
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2425
2520
  */
2426
- closeAndGetPendingLocalState(): string;
2521
+ closeAndGetPendingLocalState?(): Promise<string>;
2427
2522
  }