@fluidframework/container-loader 2.0.0-internal.5.3.2 → 2.0.0-internal.6.0.0

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 (124) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +6 -3
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +3 -1
  6. package/dist/audience.js.map +1 -1
  7. package/dist/connectionManager.d.ts +1 -1
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +30 -36
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionStateHandler.d.ts +2 -1
  12. package/dist/connectionStateHandler.d.ts.map +1 -1
  13. package/dist/connectionStateHandler.js +9 -16
  14. package/dist/connectionStateHandler.js.map +1 -1
  15. package/dist/container.d.ts +12 -8
  16. package/dist/container.d.ts.map +1 -1
  17. package/dist/container.js +199 -156
  18. package/dist/container.js.map +1 -1
  19. package/dist/containerContext.d.ts +2 -12
  20. package/dist/containerContext.d.ts.map +1 -1
  21. package/dist/containerContext.js +1 -20
  22. package/dist/containerContext.js.map +1 -1
  23. package/dist/containerStorageAdapter.js +3 -5
  24. package/dist/containerStorageAdapter.js.map +1 -1
  25. package/dist/contracts.d.ts +11 -2
  26. package/dist/contracts.d.ts.map +1 -1
  27. package/dist/contracts.js +3 -3
  28. package/dist/contracts.js.map +1 -1
  29. package/dist/debugLogger.d.ts +30 -0
  30. package/dist/debugLogger.d.ts.map +1 -0
  31. package/dist/debugLogger.js +95 -0
  32. package/dist/debugLogger.js.map +1 -0
  33. package/dist/deltaManager.d.ts +16 -4
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +79 -33
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaQueue.js +1 -2
  38. package/dist/deltaQueue.js.map +1 -1
  39. package/dist/loader.d.ts +12 -0
  40. package/dist/loader.d.ts.map +1 -1
  41. package/dist/loader.js +73 -47
  42. package/dist/loader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.js +1 -1
  45. package/dist/packageVersion.js.map +1 -1
  46. package/dist/protocol.d.ts.map +1 -1
  47. package/dist/protocol.js +2 -3
  48. package/dist/protocol.js.map +1 -1
  49. package/dist/quorum.d.ts +4 -1
  50. package/dist/quorum.d.ts.map +1 -1
  51. package/dist/quorum.js +1 -13
  52. package/dist/quorum.js.map +1 -1
  53. package/dist/utils.d.ts +8 -1
  54. package/dist/utils.d.ts.map +1 -1
  55. package/dist/utils.js +24 -6
  56. package/dist/utils.js.map +1 -1
  57. package/lib/audience.d.ts +1 -0
  58. package/lib/audience.d.ts.map +1 -1
  59. package/lib/audience.js +3 -1
  60. package/lib/audience.js.map +1 -1
  61. package/lib/connectionManager.d.ts +1 -1
  62. package/lib/connectionManager.d.ts.map +1 -1
  63. package/lib/connectionManager.js +32 -35
  64. package/lib/connectionManager.js.map +1 -1
  65. package/lib/connectionStateHandler.d.ts +2 -1
  66. package/lib/connectionStateHandler.d.ts.map +1 -1
  67. package/lib/connectionStateHandler.js +9 -16
  68. package/lib/connectionStateHandler.js.map +1 -1
  69. package/lib/container.d.ts +12 -8
  70. package/lib/container.d.ts.map +1 -1
  71. package/lib/container.js +200 -157
  72. package/lib/container.js.map +1 -1
  73. package/lib/containerContext.d.ts +2 -12
  74. package/lib/containerContext.d.ts.map +1 -1
  75. package/lib/containerContext.js +1 -20
  76. package/lib/containerContext.js.map +1 -1
  77. package/lib/containerStorageAdapter.js +3 -5
  78. package/lib/containerStorageAdapter.js.map +1 -1
  79. package/lib/contracts.d.ts +11 -2
  80. package/lib/contracts.d.ts.map +1 -1
  81. package/lib/contracts.js +3 -3
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/debugLogger.d.ts +30 -0
  84. package/lib/debugLogger.d.ts.map +1 -0
  85. package/lib/debugLogger.js +91 -0
  86. package/lib/debugLogger.js.map +1 -0
  87. package/lib/deltaManager.d.ts +16 -4
  88. package/lib/deltaManager.d.ts.map +1 -1
  89. package/lib/deltaManager.js +77 -28
  90. package/lib/deltaManager.js.map +1 -1
  91. package/lib/deltaQueue.js +1 -2
  92. package/lib/deltaQueue.js.map +1 -1
  93. package/lib/loader.d.ts +12 -0
  94. package/lib/loader.d.ts.map +1 -1
  95. package/lib/loader.js +73 -47
  96. package/lib/loader.js.map +1 -1
  97. package/lib/packageVersion.d.ts +1 -1
  98. package/lib/packageVersion.js +1 -1
  99. package/lib/packageVersion.js.map +1 -1
  100. package/lib/protocol.d.ts.map +1 -1
  101. package/lib/protocol.js +2 -3
  102. package/lib/protocol.js.map +1 -1
  103. package/lib/quorum.d.ts +4 -1
  104. package/lib/quorum.d.ts.map +1 -1
  105. package/lib/quorum.js +0 -11
  106. package/lib/quorum.js.map +1 -1
  107. package/lib/utils.d.ts +8 -1
  108. package/lib/utils.d.ts.map +1 -1
  109. package/lib/utils.js +22 -5
  110. package/lib/utils.js.map +1 -1
  111. package/package.json +14 -14
  112. package/src/audience.ts +6 -0
  113. package/src/connectionManager.ts +13 -14
  114. package/src/connectionStateHandler.ts +3 -2
  115. package/src/container.ts +178 -120
  116. package/src/containerContext.ts +0 -24
  117. package/src/contracts.ts +16 -5
  118. package/src/debugLogger.ts +113 -0
  119. package/src/deltaManager.ts +50 -9
  120. package/src/loader.ts +53 -30
  121. package/src/packageVersion.ts +1 -1
  122. package/src/protocol.ts +0 -1
  123. package/src/quorum.ts +0 -10
  124. package/src/utils.ts +29 -0
package/src/container.ts CHANGED
@@ -23,27 +23,26 @@ import {
23
23
  FluidObject,
24
24
  } from "@fluidframework/core-interfaces";
25
25
  import {
26
+ AttachState,
27
+ ContainerWarning,
26
28
  IAudience,
27
- IConnectionDetailsInternal,
29
+ IBatchMessage,
30
+ ICodeDetailsLoader,
28
31
  IContainer,
29
32
  IContainerEvents,
30
- IDeltaManager,
31
- ICriticalContainerError,
32
- ContainerWarning,
33
- AttachState,
34
- IThrottlingWarning,
35
- ReadOnlyInfo,
36
33
  IContainerLoadMode,
34
+ ICriticalContainerError,
35
+ IDeltaManager,
37
36
  IFluidCodeDetails,
38
- isFluidCodeDetails,
39
- IBatchMessage,
40
- ICodeDetailsLoader,
41
37
  IHostLoader,
42
38
  IFluidModuleWithDetails,
43
39
  IProvideRuntimeFactory,
44
40
  IProvideFluidCodeDetailsComparer,
45
41
  IFluidCodeDetailsComparer,
46
42
  IRuntime,
43
+ isFluidCodeDetails,
44
+ IThrottlingWarning,
45
+ ReadOnlyInfo,
47
46
  } from "@fluidframework/container-definitions";
48
47
  import { GenericError, UsageError } from "@fluidframework/container-utils";
49
48
  import {
@@ -58,7 +57,6 @@ import {
58
57
  readAndParse,
59
58
  OnlineStatus,
60
59
  isOnline,
61
- combineAppAndProtocolSummary,
62
60
  runWithRetry,
63
61
  isCombinedAppAndProtocolSummary,
64
62
  MessageType2,
@@ -85,21 +83,26 @@ import {
85
83
  SummaryType,
86
84
  } from "@fluidframework/protocol-definitions";
87
85
  import {
88
- ChildLogger,
86
+ createChildLogger,
89
87
  EventEmitterWithErrorHandling,
90
88
  PerformanceEvent,
91
89
  raiseConnectedEvent,
92
- TelemetryLogger,
93
90
  connectedEventName,
94
91
  normalizeError,
95
92
  MonitoringContext,
96
- loggerToMonitoringContext,
93
+ createChildMonitoringContext,
97
94
  wrapError,
98
95
  ITelemetryLoggerExt,
96
+ formatTick,
99
97
  } from "@fluidframework/telemetry-utils";
100
98
  import { Audience } from "./audience";
101
99
  import { ContainerContext } from "./containerContext";
102
- import { ReconnectMode, IConnectionManagerFactoryArgs, getPackageName } from "./contracts";
100
+ import {
101
+ ReconnectMode,
102
+ IConnectionManagerFactoryArgs,
103
+ getPackageName,
104
+ IConnectionDetailsInternal,
105
+ } from "./contracts";
103
106
  import { DeltaManager, IConnectionArgs } from "./deltaManager";
104
107
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
105
108
  import { pkgVersion } from "./packageVersion";
@@ -110,8 +113,12 @@ import {
110
113
  ISerializableBlobContents,
111
114
  } from "./containerStorageAdapter";
112
115
  import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
113
- import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
114
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
116
+ import {
117
+ combineAppAndProtocolSummary,
118
+ getProtocolSnapshotTree,
119
+ getSnapshotTreeFromSerializedContainer,
120
+ } from "./utils";
121
+ import { initQuorumValuesFromCodeDetails } from "./quorum";
115
122
  import { NoopHeuristic } from "./noopHeuristic";
116
123
  import { ConnectionManager } from "./connectionManager";
117
124
  import { ConnectionState } from "./connectionState";
@@ -151,6 +158,11 @@ export interface IContainerLoadProps {
151
158
  * The pending state serialized from a pervious container instance
152
159
  */
153
160
  readonly pendingLocalState?: IPendingContainerState;
161
+
162
+ /**
163
+ * Load the container to at least this sequence number.
164
+ */
165
+ readonly loadToSequenceNumber?: number;
154
166
  }
155
167
 
156
168
  /**
@@ -369,7 +381,8 @@ export class Container
369
381
  loadProps: IContainerLoadProps,
370
382
  createProps: IContainerCreateProps,
371
383
  ): Promise<Container> {
372
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
384
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
385
+ loadProps;
373
386
 
374
387
  const container = new Container(createProps, loadProps);
375
388
 
@@ -398,7 +411,7 @@ export class Container
398
411
  container.on("closed", onClosed);
399
412
 
400
413
  container
401
- .load(version, mode, resolvedUrl, pendingLocalState)
414
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
402
415
  .finally(() => {
403
416
  container.removeListener("closed", onClosed);
404
417
  })
@@ -473,7 +486,7 @@ export class Container
473
486
  private readonly codeLoader: ICodeDetailsLoader;
474
487
  private readonly options: ILoaderOptions;
475
488
  private readonly scope: FluidObject;
476
- private readonly subLogger: TelemetryLogger;
489
+ private readonly subLogger: ITelemetryLoggerExt;
477
490
  private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
478
491
  private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
479
492
 
@@ -523,13 +536,14 @@ export class Container
523
536
 
524
537
  public get closed(): boolean {
525
538
  return (
526
- this._lifecycleState === "closing" ||
527
- this._lifecycleState === "closed" ||
528
- this._lifecycleState === "disposing" ||
529
- this._lifecycleState === "disposed"
539
+ this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed
530
540
  );
531
541
  }
532
542
 
543
+ public get disposed(): boolean {
544
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
545
+ }
546
+
533
547
  private _attachState = AttachState.Detached;
534
548
 
535
549
  private readonly storageAdapter: ContainerStorageAdapter;
@@ -556,7 +570,6 @@ export class Container
556
570
  private inboundQueuePausedFromInit = true;
557
571
  private firstConnection = true;
558
572
  private readonly connectionTransitionTimes: number[] = [];
559
- private messageCountAfterDisconnection: number = 0;
560
573
  private _loadedFromVersion: IVersion | undefined;
561
574
  private attachStarted = false;
562
575
  private _dirtyContainer = false;
@@ -769,42 +782,45 @@ export class Container
769
782
  }`;
770
783
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
771
784
  // 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],
785
+ this.subLogger = createChildLogger({
786
+ logger: subLogger,
787
+ properties: {
788
+ all: {
789
+ clientType, // Differentiating summarizer container from main container
790
+ containerId: uuid(),
791
+ docId: () => this.resolvedUrl?.id,
792
+ containerAttachState: () => this._attachState,
793
+ containerLifecycleState: () => this._lifecycleState,
794
+ containerConnectionState: () => ConnectionState[this.connectionState],
795
+ serializedContainer: pendingLocalState !== undefined,
796
+ },
797
+ // we need to be judicious with our logging here to avoid generating too much data
798
+ // all data logged here should be broadly applicable, and not specific to a
799
+ // specific error or class of errors
800
+ error: {
801
+ // load information to associate errors with the specific load point
802
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
803
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
804
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
805
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
806
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
807
+ // message information to associate errors with the specific execution state
808
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
809
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
810
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
811
+ dmLastMsqSeqClientId: () =>
812
+ this.deltaManager?.lastMessage?.clientId === null
813
+ ? "null"
814
+ : this.deltaManager?.lastMessage?.clientId,
815
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
816
+ connectionStateDuration: () =>
817
+ performance.now() - this.connectionTransitionTimes[this.connectionState],
818
+ },
803
819
  },
804
820
  });
805
821
 
806
822
  // Prefix all events in this file with container-loader
807
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
823
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
808
824
 
809
825
  this._deltaManager = this.createDeltaManager();
810
826
 
@@ -900,8 +916,8 @@ export class Container
900
916
  document !== null &&
901
917
  typeof document.addEventListener === "function" &&
902
918
  document.addEventListener !== null;
903
- // keep track of last time page was visible for telemetry
904
- if (isDomAvailable) {
919
+ // keep track of last time page was visible for telemetry (on interactive clients only)
920
+ if (isDomAvailable && interactive) {
905
921
  this.lastVisible = document.hidden ? performance.now() : undefined;
906
922
  this.visibilityEventHandler = () => {
907
923
  if (document.hidden) {
@@ -1051,16 +1067,21 @@ export class Container
1051
1067
  }
1052
1068
  }
1053
1069
 
1054
- public closeAndGetPendingLocalState(): string {
1070
+ public async closeAndGetPendingLocalState(): Promise<string> {
1055
1071
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
1056
1072
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
1057
1073
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1058
- const pendingState = this.getPendingLocalState();
1074
+ this.disconnect(); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1075
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
1059
1076
  this.close();
1060
1077
  return pendingState;
1061
1078
  }
1062
1079
 
1063
- public getPendingLocalState(): string {
1080
+ public async getPendingLocalState(): Promise<string> {
1081
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
1082
+ }
1083
+
1084
+ private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) {
1064
1085
  if (!this.offlineLoadEnabled) {
1065
1086
  throw new UsageError("Can't get pending local state unless offline load is enabled");
1066
1087
  }
@@ -1079,8 +1100,9 @@ export class Container
1079
1100
  );
1080
1101
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1081
1102
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1103
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
1082
1104
  const pendingState: IPendingContainerState = {
1083
- pendingRuntimeState: this.runtime.getPendingLocalState(),
1105
+ pendingRuntimeState,
1084
1106
  baseSnapshot: this.baseSnapshot,
1085
1107
  snapshotBlobs: this.baseSnapshotBlobs,
1086
1108
  savedOps: this.savedOps,
@@ -1465,7 +1487,8 @@ export class Container
1465
1487
  specifiedVersion: string | undefined,
1466
1488
  loadMode: IContainerLoadMode,
1467
1489
  resolvedUrl: IResolvedUrl,
1468
- pendingLocalState?: IPendingContainerState,
1490
+ pendingLocalState: IPendingContainerState | undefined,
1491
+ loadToSequenceNumber: number | undefined,
1469
1492
  ) {
1470
1493
  this.service = await this.serviceFactory.createDocumentService(
1471
1494
  resolvedUrl,
@@ -1539,6 +1562,57 @@ export class Container
1539
1562
 
1540
1563
  let opsBeforeReturnP: Promise<void> | undefined;
1541
1564
 
1565
+ if (loadMode.pauseAfterLoad === true) {
1566
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
1567
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
1568
+ assert(
1569
+ loadToSequenceNumber !== undefined,
1570
+ 0x727 /* sequenceNumber should be defined */,
1571
+ );
1572
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
1573
+ // due to saved ops that may be replayed after the snapshot.
1574
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
1575
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
1576
+ throw new Error(
1577
+ "Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
1578
+ );
1579
+ }
1580
+ }
1581
+
1582
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
1583
+ this.forceReadonly(true);
1584
+
1585
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
1586
+ const opHandler = () => {
1587
+ if (loadToSequenceNumber === undefined) {
1588
+ // If there is no specified sequence number, pause after the inbound queue is empty.
1589
+ if (this.deltaManager.inbound.length !== 0) {
1590
+ return;
1591
+ }
1592
+ } else {
1593
+ // If there is a specified sequence number, keep processing until we reach it.
1594
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1595
+ return;
1596
+ }
1597
+ }
1598
+
1599
+ // Pause op processing once we have processed the desired number of ops.
1600
+ void this.deltaManager.inbound.pause();
1601
+ void this.deltaManager.outbound.pause();
1602
+ this.off("op", opHandler);
1603
+ };
1604
+ if (
1605
+ (loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
1606
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber
1607
+ ) {
1608
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
1609
+ opHandler();
1610
+ } else {
1611
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
1612
+ this.on("op", opHandler);
1613
+ }
1614
+ }
1615
+
1542
1616
  // Attach op handlers to finish initialization and be able to start processing ops
1543
1617
  // Kick off any ops fetching if required.
1544
1618
  switch (loadMode.opsBeforeReturn) {
@@ -1550,6 +1624,9 @@ export class Container
1550
1624
  loadMode.deltaConnection !== "none" ? "all" : "none",
1551
1625
  );
1552
1626
  break;
1627
+ case "sequenceNumber":
1628
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
1629
+ break;
1553
1630
  case "cached":
1554
1631
  opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
1555
1632
  break;
@@ -1565,8 +1642,7 @@ export class Container
1565
1642
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1566
1643
 
1567
1644
  const codeDetails = this.getCodeDetailsFromQuorum();
1568
- await this.instantiateContext(
1569
- true, // existing
1645
+ await this.instantiateRuntime(
1570
1646
  codeDetails,
1571
1647
  snapshot,
1572
1648
  pendingLocalState?.pendingRuntimeState,
@@ -1634,6 +1710,22 @@ export class Container
1634
1710
  }
1635
1711
  }
1636
1712
 
1713
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1714
+ if (
1715
+ loadToSequenceNumber !== undefined &&
1716
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber
1717
+ ) {
1718
+ await new Promise<void>((resolve, reject) => {
1719
+ const opHandler = (message: ISequencedDocumentMessage) => {
1720
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1721
+ resolve();
1722
+ this.off("op", opHandler);
1723
+ }
1724
+ };
1725
+ this.on("op", opHandler);
1726
+ });
1727
+ }
1728
+
1637
1729
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
1638
1730
  // But if that did not happen for some reason, fail load for sure.
1639
1731
  // Otherwise we can get into situations where container is closed and does not try to connect to ordering
@@ -1654,7 +1746,7 @@ export class Container
1654
1746
  };
1655
1747
  }
1656
1748
 
1657
- private async createDetached(source: IFluidCodeDetails) {
1749
+ private async createDetached(codeDetails: IFluidCodeDetails) {
1658
1750
  const attributes: IDocumentAttributes = {
1659
1751
  sequenceNumber: detachedContainerRefSeqNumber,
1660
1752
  term: OnlyValidTermValue,
@@ -1664,7 +1756,7 @@ export class Container
1664
1756
  await this.attachDeltaManagerOpHandler(attributes);
1665
1757
 
1666
1758
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1667
- const qValues = initQuorumValuesFromCodeDetails(source);
1759
+ const qValues = initQuorumValuesFromCodeDetails(codeDetails);
1668
1760
  this.initializeProtocolState(
1669
1761
  attributes,
1670
1762
  {
@@ -1674,10 +1766,7 @@ export class Container
1674
1766
  }, // IQuorumSnapShot
1675
1767
  );
1676
1768
 
1677
- // The load context - given we seeded the quorum - will be great
1678
- await this.instantiateContextDetached(
1679
- false, // existing
1680
- );
1769
+ await this.instantiateRuntime(codeDetails, undefined);
1681
1770
 
1682
1771
  this.setLoaded();
1683
1772
  }
@@ -1703,21 +1792,17 @@ export class Container
1703
1792
  this.storageAdapter,
1704
1793
  baseTree.blobs.quorumValues,
1705
1794
  );
1706
- const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1707
1795
  this.initializeProtocolState(
1708
1796
  attributes,
1709
1797
  {
1710
1798
  members: [],
1711
1799
  proposals: [],
1712
- values:
1713
- codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1800
+ values: qValues,
1714
1801
  }, // IQuorumSnapShot
1715
1802
  );
1803
+ const codeDetails = this.getCodeDetailsFromQuorum();
1716
1804
 
1717
- await this.instantiateContextDetached(
1718
- true, // existing
1719
- snapshotTree,
1720
- );
1805
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1721
1806
 
1722
1807
  this.setLoaded();
1723
1808
  }
@@ -1786,7 +1871,10 @@ export class Container
1786
1871
  this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
1787
1872
  );
1788
1873
 
1789
- const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1874
+ const protocolLogger = createChildLogger({
1875
+ logger: this.subLogger,
1876
+ namespace: "ProtocolHandler",
1877
+ });
1790
1878
 
1791
1879
  protocol.quorum.on("error", (error) => {
1792
1880
  protocolLogger.sendErrorEvent(error);
@@ -1897,7 +1985,7 @@ export class Container
1897
1985
  const serviceProvider = () => this.service;
1898
1986
  const deltaManager = new DeltaManager<ConnectionManager>(
1899
1987
  serviceProvider,
1900
- ChildLogger.create(this.subLogger, "DeltaManager"),
1988
+ createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }),
1901
1989
  () => this.activeConnection(),
1902
1990
  (props: IConnectionManagerFactoryArgs) =>
1903
1991
  new ConnectionManager(
@@ -1905,7 +1993,7 @@ export class Container
1905
1993
  () => this.isDirty,
1906
1994
  this.client,
1907
1995
  this._canReconnect,
1908
- ChildLogger.create(this.subLogger, "ConnectionManager"),
1996
+ createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }),
1909
1997
  props,
1910
1998
  ),
1911
1999
  );
@@ -1967,7 +2055,7 @@ export class Container
1967
2055
 
1968
2056
  private async attachDeltaManagerOpHandler(
1969
2057
  attributes: IDocumentAttributes,
1970
- prefetchType?: "cached" | "all" | "none",
2058
+ prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
1971
2059
  ) {
1972
2060
  return this._deltaManager.attachOpHandler(
1973
2061
  attributes.minimumSequenceNumber,
@@ -2004,7 +2092,7 @@ export class Container
2004
2092
  if (value === ConnectionState.Connected) {
2005
2093
  durationFromDisconnected =
2006
2094
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
2007
- durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
2095
+ durationFromDisconnected = formatTick(durationFromDisconnected);
2008
2096
  } else if (value === ConnectionState.CatchingUp) {
2009
2097
  // This info is of most interesting while Catching Up.
2010
2098
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
@@ -2038,6 +2126,7 @@ export class Container
2038
2126
  : undefined,
2039
2127
  checkpointSequenceNumber,
2040
2128
  quorumSize: this._protocolHandler?.quorum.getMembers().size,
2129
+ isDirty: this.isDirty,
2041
2130
  ...this._deltaManager.connectionProps,
2042
2131
  },
2043
2132
  error,
@@ -2061,26 +2150,11 @@ export class Container
2061
2150
  }
2062
2151
  const state = this.connectionState === ConnectionState.Connected;
2063
2152
 
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
2153
  // Both protocol and context should not be undefined if we got so far.
2073
2154
 
2074
2155
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2075
2156
  this.protocolHandler.setConnectionState(state, this.clientId);
2076
2157
  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
- }
2084
2158
  }
2085
2159
 
2086
2160
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2156,7 +2230,6 @@ export class Container
2156
2230
  return -1;
2157
2231
  }
2158
2232
 
2159
- this.messageCountAfterDisconnection += 1;
2160
2233
  this.noopHeuristic?.notifyMessageSent();
2161
2234
  return this._deltaManager.submit(
2162
2235
  type,
@@ -2280,17 +2353,7 @@ export class Container
2280
2353
  return { snapshot, versionId: version?.id };
2281
2354
  }
2282
2355
 
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,
2356
+ private async instantiateRuntime(
2294
2357
  codeDetails: IFluidCodeDetails,
2295
2358
  snapshot: ISnapshotTree | undefined,
2296
2359
  pendingLocalState?: unknown,
@@ -2327,6 +2390,8 @@ export class Container
2327
2390
  (this.protocolHandler.quorum.get("code") ??
2328
2391
  this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
2329
2392
 
2393
+ const existing = snapshot !== undefined;
2394
+
2330
2395
  const context = new ContainerContext(
2331
2396
  this.options,
2332
2397
  this.scope,
@@ -2348,9 +2413,7 @@ export class Container
2348
2413
  (error?: ICriticalContainerError) => this.close(error),
2349
2414
  this.updateDirtyContainerState,
2350
2415
  this.getAbsoluteUrl,
2351
- () => this.resolvedUrl?.id,
2352
2416
  () => this.clientId,
2353
- () => this._deltaManager.serviceConfiguration,
2354
2417
  () => this.attachState,
2355
2418
  () => this.connected,
2356
2419
  getSpecifiedCodeDetails,
@@ -2359,9 +2422,6 @@ export class Container
2359
2422
  this.subLogger,
2360
2423
  pendingLocalState,
2361
2424
  );
2362
- this._lifecycleEvents.once("disposed", () => {
2363
- context.dispose();
2364
- });
2365
2425
 
2366
2426
  this._runtime = await PerformanceEvent.timedExecAsync(
2367
2427
  this.subLogger,
@@ -2371,8 +2431,6 @@ export class Container
2371
2431
  this._lifecycleEvents.emit("runtimeInstantiated");
2372
2432
 
2373
2433
  this._loadedCodeDetails = codeDetails;
2374
-
2375
- this.emit("contextChanged", codeDetails);
2376
2434
  }
2377
2435
 
2378
2436
  private readonly updateDirtyContainerState = (dirty: boolean) => {
@@ -2415,7 +2473,7 @@ export interface IContainerExperimental extends IContainer {
2415
2473
  * @experimental misuse of this API can result in duplicate op submission and potential document corruption
2416
2474
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2417
2475
  */
2418
- getPendingLocalState?(): string;
2476
+ getPendingLocalState?(): Promise<string>;
2419
2477
 
2420
2478
  /**
2421
2479
  * Closes the container and returns serialized local state intended to be
@@ -2423,5 +2481,5 @@ export interface IContainerExperimental extends IContainer {
2423
2481
  * @experimental
2424
2482
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2425
2483
  */
2426
- closeAndGetPendingLocalState(): string;
2484
+ closeAndGetPendingLocalState?(): Promise<string>;
2427
2485
  }
@@ -18,7 +18,6 @@ import {
18
18
  import { FluidObject } from "@fluidframework/core-interfaces";
19
19
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
20
20
  import {
21
- IClientConfiguration,
22
21
  IClientDetails,
23
22
  IDocumentMessage,
24
23
  IQuorumClients,
@@ -46,13 +45,6 @@ export class ContainerContext implements IContainerContext {
46
45
  return this._getClientId();
47
46
  }
48
47
 
49
- /**
50
- * DISCLAIMER: this id is only for telemetry purposes. Not suitable for any other usages.
51
- */
52
- public get id(): string {
53
- return this._getContainerDiagnosticId() ?? "";
54
- }
55
-
56
48
  /**
57
49
  * When true, ops are free to flow
58
50
  * When false, ops should be kept as pending or rejected
@@ -61,16 +53,6 @@ export class ContainerContext implements IContainerContext {
61
53
  return this._getConnected();
62
54
  }
63
55
 
64
- public get serviceConfiguration(): IClientConfiguration | undefined {
65
- return this._getServiceConfiguration();
66
- }
67
-
68
- private _disposed = false;
69
-
70
- public get disposed() {
71
- return this._disposed;
72
- }
73
-
74
56
  constructor(
75
57
  public readonly options: ILoaderOptions,
76
58
  public readonly scope: FluidObject,
@@ -101,9 +83,7 @@ export class ContainerContext implements IContainerContext {
101
83
  public readonly closeFn: (error?: ICriticalContainerError) => void,
102
84
  public readonly updateDirtyContainerState: (dirty: boolean) => void,
103
85
  public readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>,
104
- private readonly _getContainerDiagnosticId: () => string | undefined,
105
86
  private readonly _getClientId: () => string | undefined,
106
- private readonly _getServiceConfiguration: () => IClientConfiguration | undefined,
107
87
  private readonly _getAttachState: () => AttachState,
108
88
  private readonly _getConnected: () => boolean,
109
89
  public readonly getSpecifiedCodeDetails: () => IFluidCodeDetails | undefined,
@@ -113,10 +93,6 @@ export class ContainerContext implements IContainerContext {
113
93
  public readonly pendingLocalState?: unknown,
114
94
  ) {}
115
95
 
116
- public dispose(error?: Error): void {
117
- this._disposed = true;
118
- }
119
-
120
96
  public getLoadedFromVersion(): IVersion | undefined {
121
97
  return this._version;
122
98
  }