@fluidframework/container-loader 1.3.0 → 2.0.0-dev.1.4.5.105745

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 (148) hide show
  1. package/.eslintrc.js +8 -21
  2. package/.mocharc.js +12 -0
  3. package/dist/audience.d.ts +2 -2
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js.map +1 -1
  6. package/dist/catchUpMonitor.d.ts +29 -0
  7. package/dist/catchUpMonitor.d.ts.map +1 -0
  8. package/dist/catchUpMonitor.js +43 -0
  9. package/dist/catchUpMonitor.js.map +1 -0
  10. package/dist/collabWindowTracker.d.ts +1 -1
  11. package/dist/collabWindowTracker.d.ts.map +1 -1
  12. package/dist/collabWindowTracker.js +12 -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 +13 -18
  17. package/dist/connectionManager.js.map +1 -1
  18. package/dist/connectionState.d.ts +0 -5
  19. package/dist/connectionState.d.ts.map +1 -1
  20. package/dist/connectionState.js +0 -5
  21. package/dist/connectionState.js.map +1 -1
  22. package/dist/connectionStateHandler.d.ts +84 -22
  23. package/dist/connectionStateHandler.d.ts.map +1 -1
  24. package/dist/connectionStateHandler.js +172 -59
  25. package/dist/connectionStateHandler.js.map +1 -1
  26. package/dist/container.d.ts +30 -17
  27. package/dist/container.d.ts.map +1 -1
  28. package/dist/container.js +173 -165
  29. package/dist/container.js.map +1 -1
  30. package/dist/containerContext.d.ts +18 -7
  31. package/dist/containerContext.d.ts.map +1 -1
  32. package/dist/containerContext.js +18 -8
  33. package/dist/containerContext.js.map +1 -1
  34. package/dist/containerStorageAdapter.d.ts +11 -25
  35. package/dist/containerStorageAdapter.d.ts.map +1 -1
  36. package/dist/containerStorageAdapter.js +51 -17
  37. package/dist/containerStorageAdapter.js.map +1 -1
  38. package/dist/contracts.d.ts +5 -5
  39. package/dist/contracts.js.map +1 -1
  40. package/dist/deltaManager.d.ts +4 -1
  41. package/dist/deltaManager.d.ts.map +1 -1
  42. package/dist/deltaManager.js +33 -6
  43. package/dist/deltaManager.js.map +1 -1
  44. package/dist/deltaQueue.js +3 -3
  45. package/dist/deltaQueue.js.map +1 -1
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/loader.d.ts +8 -1
  50. package/dist/loader.d.ts.map +1 -1
  51. package/dist/loader.js +4 -3
  52. package/dist/loader.js.map +1 -1
  53. package/dist/packageVersion.d.ts +1 -1
  54. package/dist/packageVersion.d.ts.map +1 -1
  55. package/dist/packageVersion.js +1 -1
  56. package/dist/packageVersion.js.map +1 -1
  57. package/dist/protocol.d.ts +22 -0
  58. package/dist/protocol.d.ts.map +1 -0
  59. package/dist/protocol.js +53 -0
  60. package/dist/protocol.js.map +1 -0
  61. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  62. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  63. package/dist/retriableDocumentStorageService.d.ts +2 -2
  64. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  65. package/dist/retriableDocumentStorageService.js +2 -2
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/lib/audience.d.ts +2 -2
  68. package/lib/audience.d.ts.map +1 -1
  69. package/lib/audience.js.map +1 -1
  70. package/lib/catchUpMonitor.d.ts +29 -0
  71. package/lib/catchUpMonitor.d.ts.map +1 -0
  72. package/lib/catchUpMonitor.js +39 -0
  73. package/lib/catchUpMonitor.js.map +1 -0
  74. package/lib/collabWindowTracker.d.ts +1 -1
  75. package/lib/collabWindowTracker.d.ts.map +1 -1
  76. package/lib/collabWindowTracker.js +13 -5
  77. package/lib/collabWindowTracker.js.map +1 -1
  78. package/lib/connectionManager.d.ts +5 -5
  79. package/lib/connectionManager.d.ts.map +1 -1
  80. package/lib/connectionManager.js +14 -21
  81. package/lib/connectionManager.js.map +1 -1
  82. package/lib/connectionState.d.ts +0 -5
  83. package/lib/connectionState.d.ts.map +1 -1
  84. package/lib/connectionState.js +0 -5
  85. package/lib/connectionState.js.map +1 -1
  86. package/lib/connectionStateHandler.d.ts +84 -22
  87. package/lib/connectionStateHandler.d.ts.map +1 -1
  88. package/lib/connectionStateHandler.js +171 -59
  89. package/lib/connectionStateHandler.js.map +1 -1
  90. package/lib/container.d.ts +30 -17
  91. package/lib/container.d.ts.map +1 -1
  92. package/lib/container.js +176 -168
  93. package/lib/container.js.map +1 -1
  94. package/lib/containerContext.d.ts +18 -7
  95. package/lib/containerContext.d.ts.map +1 -1
  96. package/lib/containerContext.js +19 -9
  97. package/lib/containerContext.js.map +1 -1
  98. package/lib/containerStorageAdapter.d.ts +11 -25
  99. package/lib/containerStorageAdapter.d.ts.map +1 -1
  100. package/lib/containerStorageAdapter.js +51 -16
  101. package/lib/containerStorageAdapter.js.map +1 -1
  102. package/lib/contracts.d.ts +5 -5
  103. package/lib/contracts.js.map +1 -1
  104. package/lib/deltaManager.d.ts +4 -1
  105. package/lib/deltaManager.d.ts.map +1 -1
  106. package/lib/deltaManager.js +35 -8
  107. package/lib/deltaManager.js.map +1 -1
  108. package/lib/deltaQueue.js +3 -3
  109. package/lib/deltaQueue.js.map +1 -1
  110. package/lib/index.d.ts +1 -0
  111. package/lib/index.d.ts.map +1 -1
  112. package/lib/index.js.map +1 -1
  113. package/lib/loader.d.ts +8 -1
  114. package/lib/loader.d.ts.map +1 -1
  115. package/lib/loader.js +4 -3
  116. package/lib/loader.js.map +1 -1
  117. package/lib/packageVersion.d.ts +1 -1
  118. package/lib/packageVersion.d.ts.map +1 -1
  119. package/lib/packageVersion.js +1 -1
  120. package/lib/packageVersion.js.map +1 -1
  121. package/lib/protocol.d.ts +22 -0
  122. package/lib/protocol.d.ts.map +1 -0
  123. package/lib/protocol.js +49 -0
  124. package/lib/protocol.js.map +1 -0
  125. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  126. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  127. package/lib/retriableDocumentStorageService.d.ts +2 -2
  128. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  129. package/lib/retriableDocumentStorageService.js +2 -2
  130. package/lib/retriableDocumentStorageService.js.map +1 -1
  131. package/package.json +26 -20
  132. package/src/audience.ts +2 -2
  133. package/src/catchUpMonitor.ts +59 -0
  134. package/src/collabWindowTracker.ts +15 -6
  135. package/src/connectionManager.ts +23 -27
  136. package/src/connectionState.ts +0 -6
  137. package/src/connectionStateHandler.ts +235 -70
  138. package/src/container.ts +223 -209
  139. package/src/containerContext.ts +22 -8
  140. package/src/containerStorageAdapter.ts +71 -16
  141. package/src/contracts.ts +7 -7
  142. package/src/deltaManager.ts +42 -11
  143. package/src/deltaQueue.ts +3 -3
  144. package/src/index.ts +4 -0
  145. package/src/loader.ts +14 -3
  146. package/src/packageVersion.ts +1 -1
  147. package/src/protocol.ts +97 -0
  148. package/src/retriableDocumentStorageService.ts +8 -2
@@ -3,24 +3,75 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
6
- import { IConnectionDetails } from "@fluidframework/container-definitions";
7
- import { IProtocolHandler } from "@fluidframework/protocol-base";
8
- import { ConnectionMode, IQuorumClients } from "@fluidframework/protocol-definitions";
6
+ import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
7
+ import { ConnectionMode } from "@fluidframework/protocol-definitions";
9
8
  import { ConnectionState } from "./connectionState";
9
+ import { IProtocolHandler } from "./protocol";
10
10
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
11
11
  export interface IConnectionStateHandlerInputs {
12
- /** Provides access to the clients currently in the quorum */
13
- quorumClients: () => IQuorumClients | undefined;
12
+ logger: ITelemetryLogger;
14
13
  /** Log to telemetry any change in state, included to Connecting */
15
- logConnectionStateChangeTelemetry: (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;
14
+ connectionStateChanged: (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;
16
15
  /** Whether to expect the client to join in write mode on next connection */
17
16
  shouldClientJoinWrite: () => boolean;
18
17
  /** (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again */
19
18
  maxClientLeaveWaitTime: number | undefined;
20
19
  /** Log an issue encountered while in the Connecting state. details will be logged as a JSON string */
21
20
  logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => void;
22
- /** Callback whenever the ConnectionState changes between Disconnected and Connected */
23
- connectionStateChanged: () => void;
21
+ }
22
+ /**
23
+ * interface that connection state handler implements
24
+ */
25
+ export interface IConnectionStateHandler {
26
+ readonly connectionState: ConnectionState;
27
+ readonly pendingClientId: string | undefined;
28
+ containerSaved(): void;
29
+ dispose(): void;
30
+ initProtocol(protocol: IProtocolHandler): void;
31
+ receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
32
+ receivedDisconnectEvent(reason: string): void;
33
+ }
34
+ export declare function createConnectionStateHandler(inputs: IConnectionStateHandlerInputs, deltaManager: IDeltaManager<any, any>, clientId?: string): ConnectionStateHandler | ConnectionStateCatchup;
35
+ export declare function createConnectionStateHandlerCore(wait: boolean, inputs: IConnectionStateHandlerInputs, deltaManager: IDeltaManager<any, any>, clientId?: string): ConnectionStateHandler | ConnectionStateCatchup;
36
+ /**
37
+ * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.
38
+ * It implements both ends of communication interfaces and passes data back and forward
39
+ */
40
+ declare class ConnectionStateHandlerPassThrough implements IConnectionStateHandler, IConnectionStateHandlerInputs {
41
+ protected readonly inputs: IConnectionStateHandlerInputs;
42
+ protected readonly pimpl: IConnectionStateHandler;
43
+ constructor(inputs: IConnectionStateHandlerInputs, pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler);
44
+ /**
45
+ * IConnectionStateHandler
46
+ */
47
+ get connectionState(): ConnectionState;
48
+ get pendingClientId(): string | undefined;
49
+ containerSaved(): void;
50
+ dispose(): void;
51
+ initProtocol(protocol: IProtocolHandler): void;
52
+ receivedDisconnectEvent(reason: string): void;
53
+ receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
54
+ /**
55
+ * IConnectionStateHandlerInputs
56
+ */
57
+ get logger(): ITelemetryLogger;
58
+ connectionStateChanged(value: ConnectionState, oldState: ConnectionState, reason?: string | undefined): void;
59
+ shouldClientJoinWrite(): boolean;
60
+ get maxClientLeaveWaitTime(): number | undefined;
61
+ logConnectionIssue(eventName: string, details?: ITelemetryProperties): void;
62
+ }
63
+ /**
64
+ * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number
65
+ * before raising connected event
66
+ */
67
+ declare class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
68
+ private readonly deltaManager;
69
+ private catchUpMonitor;
70
+ constructor(inputs: IConnectionStateHandlerInputs, pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler, deltaManager: IDeltaManager<any, any>);
71
+ private _connectionState;
72
+ get connectionState(): ConnectionState;
73
+ connectionStateChanged(value: ConnectionState, oldState: ConnectionState, reason?: string | undefined): void;
74
+ private readonly transitionToConnectedState;
24
75
  }
25
76
  /**
26
77
  * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
@@ -28,36 +79,42 @@ export interface IConnectionStateHandlerInputs {
28
79
  * sequenced or blocked by the server before emitting the new "connected" event and allowing runtime to resubmit ops.
29
80
  *
30
81
  * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op
31
- * generated by the service. Due to the distributed nature of the ordering service, in the case of reconnect we cannot
82
+ * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot
32
83
  * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could
33
84
  * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).
34
85
  *
35
86
  * The job of this class is to encapsulate the transition period during reconnect, which is identified by
36
87
  * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
37
- * (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
38
- * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
39
- * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
40
- * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
41
- * indicating the service is ready to sequence ops sent with the new clientId.
42
88
  *
43
- * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
44
- * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
89
+ * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
90
+ * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
91
+ * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
92
+ *
93
+ * b. We process the Join op for the new clientId (identified when the underlying connection was first established),
94
+ * indicating the service is ready to sequence ops sent with the new clientId.
95
+ *
96
+ * c. We process all ops known at the time the underlying connection was established (so we are "caught up")
97
+ *
98
+ * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
99
+ *
100
+ * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
45
101
  * and we are added to the Quorum.
102
+ *
103
+ * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
46
104
  */
47
- export declare class ConnectionStateHandler {
105
+ declare class ConnectionStateHandler implements IConnectionStateHandler {
48
106
  private readonly handler;
49
- private readonly logger;
50
107
  private _clientId?;
51
108
  private _connectionState;
52
109
  private _pendingClientId;
53
110
  private readonly prevClientLeftTimer;
54
111
  private readonly joinOpTimer;
112
+ private protocol?;
55
113
  private waitEvent;
56
114
  get connectionState(): ConnectionState;
57
- get connected(): boolean;
58
- get clientId(): string | undefined;
115
+ private get clientId();
59
116
  get pendingClientId(): string | undefined;
60
- constructor(handler: IConnectionStateHandlerInputs, logger: ITelemetryLogger, _clientId?: string | undefined);
117
+ constructor(handler: IConnectionStateHandlerInputs, _clientId?: string | undefined);
61
118
  private startJoinOpTimer;
62
119
  private stopJoinOpTimer;
63
120
  private get waitingForLeaveOp();
@@ -72,10 +129,15 @@ export declare class ConnectionStateHandler {
72
129
  * However, some additional conditions must be met before we can fully transition to
73
130
  * "Connected" state. This function handles that interim period, known as "Connecting" state.
74
131
  * @param connectionMode - Read or Write connection
75
- * @param details - Connection details returned from the ordering service
132
+ * @param details - Connection details returned from the Relay Service
133
+ * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
134
+ * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
76
135
  */
77
136
  receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
78
137
  private setConnectionState;
138
+ protected get membership(): import("@fluidframework/protocol-definitions").IQuorum | undefined;
79
139
  initProtocol(protocol: IProtocolHandler): void;
140
+ protected hasMember(clientId?: string): boolean;
80
141
  }
142
+ export {};
81
143
  //# sourceMappingURL=connectionStateHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAyB,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,kGAAkG;AAClG,MAAM,WAAW,6BAA6B;IAC1C,6DAA6D;IAC7D,aAAa,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAChD,mEAAmE;IACnE,iCAAiC,EAC7B,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC7F,4EAA4E;IAC5E,qBAAqB,EAAE,MAAM,OAAO,CAAC;IACrC,mHAAmH;IACnH,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,sGAAsG;IACtG,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChF,uFAAuF;IACvF,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,sBAAsB;IAyB3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,SAAS,CAAC;IA1BtB,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAW,eAAe,IAAI,eAAe,CAE5C;IAED,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,IAAW,QAAQ,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,IAAW,eAAe,IAAI,MAAM,GAAG,SAAS,CAE/C;gBAGoB,OAAO,EAAE,6BAA6B,EACtC,MAAM,EAAE,gBAAgB,EACjC,SAAS,CAAC,oBAAQ;IAoC9B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAKvB,OAAO,KAAK,iBAAiB,GAE5B;IAEM,OAAO;IAKP,cAAc;IASrB,OAAO,CAAC,sBAAsB;IAwB9B,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,yBAAyB;IAQ1B,uBAAuB,CAAC,MAAM,EAAE,MAAM;IAO7C;;;;;;OAMG;IACI,oBAAoB,CACvB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,kBAAkB;IA6C/B,OAAO,CAAC,kBAAkB;IA0DnB,YAAY,CAAC,QAAQ,EAAE,gBAAgB;CAcjD"}
1
+ {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
@@ -3,60 +3,157 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { assert, Timer } from "@fluidframework/common-utils";
6
- import { PerformanceEvent } from "@fluidframework/telemetry-utils";
6
+ import { PerformanceEvent, loggerToMonitoringContext } from "@fluidframework/telemetry-utils";
7
7
  import { ConnectionState } from "./connectionState";
8
+ import { CatchUpMonitor } from "./catchUpMonitor";
8
9
  const JoinOpTimeoutMs = 45000;
10
+ export function createConnectionStateHandler(inputs, deltaManager, clientId) {
11
+ const mc = loggerToMonitoringContext(inputs.logger);
12
+ return createConnectionStateHandlerCore(mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true, inputs, deltaManager, clientId);
13
+ }
14
+ export function createConnectionStateHandlerCore(wait, inputs, deltaManager, clientId) {
15
+ if (!wait) {
16
+ return new ConnectionStateHandler(inputs, clientId);
17
+ }
18
+ return new ConnectionStateCatchup(inputs, (handler) => new ConnectionStateHandler(handler, clientId), deltaManager);
19
+ }
20
+ /**
21
+ * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.
22
+ * It implements both ends of communication interfaces and passes data back and forward
23
+ */
24
+ class ConnectionStateHandlerPassThrough {
25
+ constructor(inputs, pimplFactory) {
26
+ this.inputs = inputs;
27
+ this.pimpl = pimplFactory(this);
28
+ }
29
+ /**
30
+ * IConnectionStateHandler
31
+ */
32
+ get connectionState() { return this.pimpl.connectionState; }
33
+ get pendingClientId() { return this.pimpl.pendingClientId; }
34
+ containerSaved() { return this.pimpl.containerSaved(); }
35
+ dispose() { return this.pimpl.dispose(); }
36
+ initProtocol(protocol) { return this.pimpl.initProtocol(protocol); }
37
+ receivedDisconnectEvent(reason) { return this.pimpl.receivedDisconnectEvent(reason); }
38
+ receivedConnectEvent(connectionMode, details) {
39
+ return this.pimpl.receivedConnectEvent(connectionMode, details);
40
+ }
41
+ /**
42
+ * IConnectionStateHandlerInputs
43
+ */
44
+ get logger() { return this.inputs.logger; }
45
+ connectionStateChanged(value, oldState, reason) {
46
+ return this.inputs.connectionStateChanged(value, oldState, reason);
47
+ }
48
+ shouldClientJoinWrite() { return this.inputs.shouldClientJoinWrite(); }
49
+ get maxClientLeaveWaitTime() { return this.inputs.maxClientLeaveWaitTime; }
50
+ logConnectionIssue(eventName, details) {
51
+ return this.inputs.logConnectionIssue(eventName, details);
52
+ }
53
+ }
54
+ /**
55
+ * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number
56
+ * before raising connected event
57
+ */
58
+ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
59
+ constructor(inputs, pimplFactory, deltaManager) {
60
+ super(inputs, pimplFactory);
61
+ this.deltaManager = deltaManager;
62
+ this.transitionToConnectedState = () => {
63
+ // Defensive measure, we should always be in Connecting state when this is called.
64
+ const state = this.pimpl.connectionState;
65
+ assert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);
66
+ assert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);
67
+ this._connectionState = ConnectionState.Connected;
68
+ this.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, "caught up");
69
+ };
70
+ this._connectionState = this.pimpl.connectionState;
71
+ }
72
+ get connectionState() {
73
+ return this._connectionState;
74
+ }
75
+ connectionStateChanged(value, oldState, reason) {
76
+ var _a;
77
+ switch (value) {
78
+ case ConnectionState.Connected:
79
+ assert(this._connectionState === ConnectionState.CatchingUp, 0x3e1 /* connectivity transitions */);
80
+ // Create catch-up monitor here (not earlier), as we might get more exact info by now about how far
81
+ // client is behind through join signal. This is only true if base layer uses signals (i.e. audience,
82
+ // not quorum, including for "rea" connections) to make decisions about moving to "connected" state.
83
+ // In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as
84
+ // we might get callback right away, and it will screw up state transition (as code outside of switch
85
+ // statement will overwrite current state).
86
+ assert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);
87
+ this.catchUpMonitor = new CatchUpMonitor(this.deltaManager, this.transitionToConnectedState);
88
+ return;
89
+ case ConnectionState.Disconnected:
90
+ (_a = this.catchUpMonitor) === null || _a === void 0 ? void 0 : _a.dispose();
91
+ this.catchUpMonitor = undefined;
92
+ break;
93
+ case ConnectionState.CatchingUp:
94
+ assert(this._connectionState === ConnectionState.Disconnected, 0x3e3 /* connectivity transitions */);
95
+ break;
96
+ default:
97
+ }
98
+ this._connectionState = value;
99
+ this.inputs.connectionStateChanged(value, oldState, reason);
100
+ }
101
+ }
9
102
  /**
10
103
  * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
11
104
  * This class ensures that any ops sent by this container instance on previous connection are either
12
105
  * sequenced or blocked by the server before emitting the new "connected" event and allowing runtime to resubmit ops.
13
106
  *
14
107
  * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op
15
- * generated by the service. Due to the distributed nature of the ordering service, in the case of reconnect we cannot
108
+ * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot
16
109
  * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could
17
110
  * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).
18
111
  *
19
112
  * The job of this class is to encapsulate the transition period during reconnect, which is identified by
20
113
  * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
21
- * (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
22
- * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
23
- * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
24
- * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
25
- * indicating the service is ready to sequence ops sent with the new clientId.
26
114
  *
27
- * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
28
- * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
115
+ * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
116
+ * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
117
+ * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
118
+ *
119
+ * b. We process the Join op for the new clientId (identified when the underlying connection was first established),
120
+ * indicating the service is ready to sequence ops sent with the new clientId.
121
+ *
122
+ * c. We process all ops known at the time the underlying connection was established (so we are "caught up")
123
+ *
124
+ * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
125
+ *
126
+ * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
29
127
  * and we are added to the Quorum.
128
+ *
129
+ * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
30
130
  */
31
- export class ConnectionStateHandler {
32
- constructor(handler, logger, _clientId) {
131
+ class ConnectionStateHandler {
132
+ constructor(handler, _clientId) {
33
133
  var _a;
34
134
  this.handler = handler;
35
- this.logger = logger;
36
135
  this._clientId = _clientId;
37
136
  this._connectionState = ConnectionState.Disconnected;
38
137
  this.prevClientLeftTimer = new Timer(
39
138
  // Default is 5 min for which we are going to wait for its own "leave" message. This is same as
40
139
  // the max time on server after which leave op is sent.
41
140
  (_a = this.handler.maxClientLeaveWaitTime) !== null && _a !== void 0 ? _a : 300000, () => {
42
- assert(!this.connected, 0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
141
+ assert(this.connectionState !== ConnectionState.Connected, 0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
43
142
  this.applyForConnectedState("timeout");
44
143
  });
45
144
  // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
46
145
  // timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
47
146
  // if retrying fixes the problem, we should not see these events.
48
147
  this.joinOpTimer = new Timer(JoinOpTimeoutMs, () => {
49
- var _a;
50
148
  // I've observed timer firing within couple ms from disconnect event, looks like
51
149
  // queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.
52
150
  if (this.connectionState !== ConnectionState.CatchingUp) {
53
151
  return;
54
152
  }
55
- const quorumClients = this.handler.quorumClients();
56
153
  const details = {
57
- quorumInitialized: quorumClients !== undefined,
58
- hasPendingClientId: this.pendingClientId !== undefined,
59
- inQuorum: (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember((_a = this.pendingClientId) !== null && _a !== void 0 ? _a : "")) !== undefined,
154
+ protocolInitialized: this.protocol !== undefined,
155
+ pendingClientId: this.pendingClientId,
156
+ clientJoined: this.hasMember(this.pendingClientId),
60
157
  waitingForLeaveOp: this.waitingForLeaveOp,
61
158
  };
62
159
  this.handler.logConnectionIssue("NoJoinOp", details);
@@ -65,9 +162,6 @@ export class ConnectionStateHandler {
65
162
  get connectionState() {
66
163
  return this._connectionState;
67
164
  }
68
- get connected() {
69
- return this.connectionState === ConnectionState.Connected;
70
- }
71
165
  get clientId() {
72
166
  return this._clientId;
73
167
  }
@@ -110,7 +204,7 @@ export class ConnectionStateHandler {
110
204
  }
111
205
  // Start the event in case we are waiting for leave or timeout.
112
206
  if (this.waitingForLeaveOp) {
113
- this.waitEvent = PerformanceEvent.start(this.logger, {
207
+ this.waitEvent = PerformanceEvent.start(this.handler.logger, {
114
208
  eventName: "WaitBeforeClientLeave",
115
209
  details: JSON.stringify({
116
210
  waitOnClientId: this._clientId,
@@ -122,24 +216,21 @@ export class ConnectionStateHandler {
122
216
  }
123
217
  }
124
218
  applyForConnectedState(source) {
125
- var _a, _b;
126
- const quorumClients = this.handler.quorumClients();
127
- assert(quorumClients !== undefined, 0x236 /* "In all cases it should be already installed" */);
128
- assert(this.waitingForLeaveOp === false ||
129
- (this.clientId !== undefined && quorumClients.getMember(this.clientId) !== undefined), 0x2e2 /* "Must only wait for leave message when clientId in quorum" */);
219
+ var _a;
220
+ assert(this.protocol !== undefined, 0x236 /* "In all cases it should be already installed" */);
221
+ assert(!this.waitingForLeaveOp || this.hasMember(this.clientId), 0x2e2 /* "Must only wait for leave message when clientId in quorum" */);
130
222
  // Move to connected state only if we are in Connecting state, we have seen our join op
131
223
  // and there is no timer running which means we are not waiting for previous client to leave
132
224
  // or timeout has occurred while doing so.
133
225
  if (this.pendingClientId !== this.clientId
134
- && this.pendingClientId !== undefined
135
- && quorumClients.getMember(this.pendingClientId) !== undefined
226
+ && this.hasMember(this.pendingClientId)
136
227
  && !this.waitingForLeaveOp) {
137
228
  (_a = this.waitEvent) === null || _a === void 0 ? void 0 : _a.end({ source });
138
229
  this.setConnectionState(ConnectionState.Connected);
139
230
  }
140
231
  else {
141
232
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
142
- this.logger.sendTelemetryEvent({
233
+ this.handler.logger.sendTelemetryEvent({
143
234
  eventName: "connectedStateRejected",
144
235
  category: source === "timeout" ? "error" : "generic",
145
236
  details: JSON.stringify({
@@ -147,7 +238,7 @@ export class ConnectionStateHandler {
147
238
  pendingClientId: this.pendingClientId,
148
239
  clientId: this.clientId,
149
240
  waitingForLeaveOp: this.waitingForLeaveOp,
150
- inQuorum: (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember((_b = this.pendingClientId) !== null && _b !== void 0 ? _b : "")) !== undefined,
241
+ clientJoined: this.hasMember(this.pendingClientId),
151
242
  }),
152
243
  });
153
244
  }
@@ -160,9 +251,6 @@ export class ConnectionStateHandler {
160
251
  }
161
252
  }
162
253
  receivedDisconnectEvent(reason) {
163
- if (this.joinOpTimer.hasTimer) {
164
- this.stopJoinOpTimer();
165
- }
166
254
  this.setConnectionState(ConnectionState.Disconnected, reason);
167
255
  }
168
256
  /**
@@ -170,17 +258,20 @@ export class ConnectionStateHandler {
170
258
  * However, some additional conditions must be met before we can fully transition to
171
259
  * "Connected" state. This function handles that interim period, known as "Connecting" state.
172
260
  * @param connectionMode - Read or Write connection
173
- * @param details - Connection details returned from the ordering service
261
+ * @param details - Connection details returned from the Relay Service
262
+ * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
263
+ * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
174
264
  */
175
265
  receivedConnectEvent(connectionMode, details) {
176
266
  const oldState = this._connectionState;
177
267
  this._connectionState = ConnectionState.CatchingUp;
178
268
  const writeConnection = connectionMode === "write";
179
- assert(writeConnection || !this.handler.shouldClientJoinWrite(), 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
180
- assert(writeConnection || !this.waitingForLeaveOp, 0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
181
- // Note that this may be undefined since the connection is established proactively on load
182
- // and the quorum may still be under initialization.
183
- const quorumClients = this.handler.quorumClients();
269
+ // The following checks are wrong. They are only valid if user has write access to a file.
270
+ // If user lost such access mid-session, user will not be able to get "write" connection.
271
+ // assert(!this.handler.shouldClientJoinWrite() || writeConnection,
272
+ // 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
273
+ // assert(!this.waitingForLeaveOp || writeConnection,
274
+ // 0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
184
275
  // Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected
185
276
  // (have received the join message for the client ID)
186
277
  // This is especially important in the reconnect case. It's possible there could be outstanding
@@ -189,13 +280,13 @@ export class ConnectionStateHandler {
189
280
  // we know there can no longer be outstanding ops that we sent with the previous client id.
190
281
  this._pendingClientId = details.clientId;
191
282
  // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
192
- this.handler.logConnectionStateChangeTelemetry(ConnectionState.CatchingUp, oldState);
283
+ this.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState);
193
284
  // For write connections, this pending clientId could be in the quorum already (i.e. join op already processed).
194
285
  // We are fetching ops from storage in parallel to connecting to Relay Service,
195
286
  // and given async processes, it's possible that we have already processed our own join message before
196
287
  // connection was fully established.
197
- // If quorumClients itself is undefined, we expect it will process the join op after it's initialized.
198
- const waitingForJoinOp = writeConnection && (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember(this._pendingClientId)) === undefined;
288
+ // If protocol is not initialized yet, we expect it will process the join op after it's initialized.
289
+ const waitingForJoinOp = writeConnection && !this.hasMember(this._pendingClientId);
199
290
  if (waitingForJoinOp) {
200
291
  // Previous client left, and we are waiting for our own join op. When it is processed we'll join the quorum
201
292
  // and attempt to transition to Connected state via receivedAddMemberEvent.
@@ -207,19 +298,20 @@ export class ConnectionStateHandler {
207
298
  // If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.
208
299
  this.setConnectionState(ConnectionState.Connected);
209
300
  }
301
+ // else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later
210
302
  }
211
303
  setConnectionState(value, reason) {
304
+ var _a, _b;
212
305
  if (this.connectionState === value) {
213
306
  // Already in the desired state - exit early
214
- this.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
307
+ this.handler.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
215
308
  return;
216
309
  }
217
310
  const oldState = this._connectionState;
218
311
  this._connectionState = value;
219
- const quorumClients = this.handler.quorumClients();
220
312
  let client;
221
313
  if (this._clientId !== undefined) {
222
- client = quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember(this._clientId);
314
+ client = (_b = (_a = this.protocol) === null || _a === void 0 ? void 0 : _a.quorum) === null || _b === void 0 ? void 0 : _b.getMember(this._clientId);
223
315
  }
224
316
  if (value === ConnectionState.Connected) {
225
317
  assert(oldState === ConnectionState.CatchingUp, 0x1d8 /* "Should only transition from Connecting state" */);
@@ -230,24 +322,27 @@ export class ConnectionStateHandler {
230
322
  this._clientId = this.pendingClientId;
231
323
  }
232
324
  else if (value === ConnectionState.Disconnected) {
233
- // Important as we process our own joinSession message through delta request
325
+ // Clear pending state immediately to prepare for reconnect
234
326
  this._pendingClientId = undefined;
235
- // Only wait for "leave" message if the connected client exists in the quorum because only the write
236
- // client will exist in the quorum and only for those clients we will receive "removeMember" event and
237
- // the client has some unacked ops.
238
- // Also server would not accept ops from read client. Also check if the timer is not already running as
327
+ if (this.joinOpTimer.hasTimer) {
328
+ this.stopJoinOpTimer();
329
+ }
330
+ // Only wait for "leave" message if the connected client exists in the quorum and had some non-acked ops
331
+ // Also check if the timer is not already running as
239
332
  // we could receive "Disconnected" event multiple times without getting connected and in that case we
240
333
  // don't want to reset the timer as we still want to wait on original client which started this timer.
241
334
  if (client !== undefined
242
335
  && this.handler.shouldClientJoinWrite()
243
- && this.prevClientLeftTimer.hasTimer === false) {
336
+ && !this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer
337
+ ) {
244
338
  this.prevClientLeftTimer.restart();
245
339
  }
246
340
  else {
247
341
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
248
- this.logger.sendTelemetryEvent({
342
+ this.handler.logger.sendTelemetryEvent({
249
343
  eventName: "noWaitOnDisconnected",
250
344
  details: JSON.stringify({
345
+ clientId: this._clientId,
251
346
  inQuorum: client !== undefined,
252
347
  waitingForLeaveOp: this.waitingForLeaveOp,
253
348
  hadOutstandingOps: this.handler.shouldClientJoinWrite(),
@@ -256,21 +351,38 @@ export class ConnectionStateHandler {
256
351
  }
257
352
  }
258
353
  // Report transition before we propagate event across layers
259
- this.handler.logConnectionStateChangeTelemetry(this._connectionState, oldState, reason);
260
- // Propagate event across layers
261
- this.handler.connectionStateChanged();
354
+ this.handler.connectionStateChanged(this._connectionState, oldState, reason);
355
+ }
356
+ // Helper method to switch between quorum and audience.
357
+ // Old design was checking only quorum for "write" clients.
358
+ // Latest change checks audience for all types of connections.
359
+ get membership() {
360
+ var _a;
361
+ return (_a = this.protocol) === null || _a === void 0 ? void 0 : _a.quorum;
262
362
  }
263
363
  initProtocol(protocol) {
264
- protocol.quorum.on("addMember", (clientId, _details) => {
364
+ var _a, _b;
365
+ this.protocol = protocol;
366
+ (_a = this.membership) === null || _a === void 0 ? void 0 : _a.on("addMember", (clientId) => {
265
367
  this.receivedAddMemberEvent(clientId);
266
368
  });
267
- protocol.quorum.on("removeMember", (clientId) => {
369
+ (_b = this.membership) === null || _b === void 0 ? void 0 : _b.on("removeMember", (clientId) => {
268
370
  this.receivedRemoveMemberEvent(clientId);
269
371
  });
372
+ // Very unlikely race condition, but theoretically can happen - our new connection is already
373
+ // summarized and we are loading from such summary.
374
+ if (this.hasMember(this.pendingClientId)) {
375
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
376
+ this.receivedAddMemberEvent(this.pendingClientId);
377
+ }
270
378
  // if we have a clientId from a previous container we need to wait for its leave message
271
- if (this.clientId !== undefined && protocol.quorum.getMember(this.clientId) !== undefined) {
379
+ if (this.clientId !== undefined && this.hasMember(this.clientId)) {
272
380
  this.prevClientLeftTimer.restart();
273
381
  }
274
382
  }
383
+ hasMember(clientId) {
384
+ var _a;
385
+ return ((_a = this.membership) === null || _a === void 0 ? void 0 : _a.getMember(clientId !== null && clientId !== void 0 ? clientId : "")) !== undefined;
386
+ }
275
387
  }
276
388
  //# sourceMappingURL=connectionStateHandler.js.map