@fluidframework/container-loader 2.0.0-internal.1.2.0.93071 → 2.0.0-internal.1.2.1
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.
- package/dist/catchUpMonitor.d.ts +6 -17
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +5 -36
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +3 -6
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +80 -26
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +170 -89
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +12 -11
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +76 -100
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +10 -24
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +50 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.js +4 -4
- package/dist/deltaManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +6 -17
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +5 -35
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +3 -6
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +80 -26
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +170 -90
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +12 -11
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +77 -101
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +10 -24
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +50 -15
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.js +4 -4
- package/lib/deltaManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +11 -11
- package/src/catchUpMonitor.ts +7 -47
- package/src/connectionManager.ts +3 -5
- package/src/connectionStateHandler.ts +231 -106
- package/src/container.ts +89 -118
- package/src/containerStorageAdapter.ts +64 -15
- package/src/deltaManager.ts +4 -4
- package/src/packageVersion.ts +1 -1
|
@@ -4,23 +4,74 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
6
6
|
import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
|
|
7
|
-
import {
|
|
8
|
-
import { ConnectionMode, IQuorumClients } from "@fluidframework/protocol-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
|
-
|
|
13
|
-
quorumClients: () => IQuorumClients | undefined;
|
|
12
|
+
logger: ITelemetryLogger;
|
|
14
13
|
/** Log to telemetry any change in state, included to Connecting */
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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.
|
|
@@ -34,33 +85,36 @@ export interface IConnectionStateHandlerInputs {
|
|
|
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
|
-
* (C) We process all ops known at the time the underlying connection was established (so we are "caught up")
|
|
43
88
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
|
46
101
|
* and we are added to the Quorum.
|
|
47
|
-
*
|
|
102
|
+
*
|
|
103
|
+
* For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
|
|
48
104
|
*/
|
|
49
|
-
|
|
105
|
+
declare class ConnectionStateHandler implements IConnectionStateHandler {
|
|
50
106
|
private readonly handler;
|
|
51
|
-
private readonly logger;
|
|
52
107
|
private _clientId?;
|
|
53
108
|
private _connectionState;
|
|
54
109
|
private _pendingClientId;
|
|
55
|
-
private catchUpMonitor;
|
|
56
110
|
private readonly prevClientLeftTimer;
|
|
57
111
|
private readonly joinOpTimer;
|
|
112
|
+
private protocol?;
|
|
58
113
|
private waitEvent;
|
|
59
114
|
get connectionState(): ConnectionState;
|
|
60
|
-
get
|
|
61
|
-
get clientId(): string | undefined;
|
|
115
|
+
private get clientId();
|
|
62
116
|
get pendingClientId(): string | undefined;
|
|
63
|
-
constructor(handler: IConnectionStateHandlerInputs,
|
|
117
|
+
constructor(handler: IConnectionStateHandlerInputs, _clientId?: string | undefined);
|
|
64
118
|
private startJoinOpTimer;
|
|
65
119
|
private stopJoinOpTimer;
|
|
66
120
|
private get waitingForLeaveOp();
|
|
@@ -70,7 +124,6 @@ export declare class ConnectionStateHandler {
|
|
|
70
124
|
private applyForConnectedState;
|
|
71
125
|
private receivedRemoveMemberEvent;
|
|
72
126
|
receivedDisconnectEvent(reason: string): void;
|
|
73
|
-
private readonly transitionToConnectedState;
|
|
74
127
|
/**
|
|
75
128
|
* The "connect" event indicates the connection to the Relay Service is live.
|
|
76
129
|
* However, some additional conditions must be met before we can fully transition to
|
|
@@ -80,10 +133,11 @@ export declare class ConnectionStateHandler {
|
|
|
80
133
|
* @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
|
|
81
134
|
* If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
|
|
82
135
|
*/
|
|
83
|
-
receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails
|
|
84
|
-
/** Clear all the state used during the Connecting phase (set in receivedConnectEvent) */
|
|
85
|
-
private clearPendingConnectionState;
|
|
136
|
+
receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
|
|
86
137
|
private setConnectionState;
|
|
138
|
+
protected get membership(): import("@fluidframework/protocol-definitions").IQuorum | undefined;
|
|
87
139
|
initProtocol(protocol: IProtocolHandler): void;
|
|
140
|
+
protected hasMember(clientId?: string): boolean;
|
|
88
141
|
}
|
|
142
|
+
export {};
|
|
89
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,aAAa,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,102 @@
|
|
|
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
|
|
8
|
+
import { CatchUpMonitor } from "./catchUpMonitor";
|
|
9
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
|
+
}
|
|
10
102
|
/**
|
|
11
103
|
* In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
|
|
12
104
|
* This class ensures that any ops sent by this container instance on previous connection are either
|
|
@@ -19,59 +111,49 @@ const JoinOpTimeoutMs = 45000;
|
|
|
19
111
|
*
|
|
20
112
|
* The job of this class is to encapsulate the transition period during reconnect, which is identified by
|
|
21
113
|
* ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
|
|
22
|
-
* (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
|
|
23
|
-
* that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
|
|
24
|
-
* pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
|
|
25
|
-
* (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
|
|
26
|
-
* indicating the service is ready to sequence ops sent with the new clientId.
|
|
27
|
-
* (C) We process all ops known at the time the underlying connection was established (so we are "caught up")
|
|
28
114
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
|
31
127
|
* and we are added to the Quorum.
|
|
32
|
-
*
|
|
128
|
+
*
|
|
129
|
+
* For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
|
|
33
130
|
*/
|
|
34
|
-
|
|
35
|
-
constructor(handler,
|
|
131
|
+
class ConnectionStateHandler {
|
|
132
|
+
constructor(handler, _clientId) {
|
|
36
133
|
var _a;
|
|
37
134
|
this.handler = handler;
|
|
38
|
-
this.logger = logger;
|
|
39
135
|
this._clientId = _clientId;
|
|
40
136
|
this._connectionState = ConnectionState.Disconnected;
|
|
41
|
-
this.transitionToConnectedState = () => {
|
|
42
|
-
// Defensive measure, we should always be in CatchingUp state when this is called.
|
|
43
|
-
if (this._connectionState === ConnectionState.CatchingUp) {
|
|
44
|
-
this.setConnectionState(ConnectionState.Connected);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
this.logger.sendTelemetryEvent({
|
|
48
|
-
eventName: "cannotTransitionToConnectedState",
|
|
49
|
-
connectionState: ConnectionState[this._connectionState],
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
137
|
this.prevClientLeftTimer = new Timer(
|
|
54
138
|
// Default is 5 min for which we are going to wait for its own "leave" message. This is same as
|
|
55
139
|
// the max time on server after which leave op is sent.
|
|
56
140
|
(_a = this.handler.maxClientLeaveWaitTime) !== null && _a !== void 0 ? _a : 300000, () => {
|
|
57
|
-
assert(
|
|
141
|
+
assert(this.connectionState !== ConnectionState.Connected, 0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
|
|
58
142
|
this.applyForConnectedState("timeout");
|
|
59
143
|
});
|
|
60
144
|
// Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
|
|
61
145
|
// timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
|
|
62
146
|
// if retrying fixes the problem, we should not see these events.
|
|
63
147
|
this.joinOpTimer = new Timer(JoinOpTimeoutMs, () => {
|
|
64
|
-
var _a;
|
|
65
148
|
// I've observed timer firing within couple ms from disconnect event, looks like
|
|
66
149
|
// queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.
|
|
67
150
|
if (this.connectionState !== ConnectionState.CatchingUp) {
|
|
68
151
|
return;
|
|
69
152
|
}
|
|
70
|
-
const quorumClients = this.handler.quorumClients();
|
|
71
153
|
const details = {
|
|
72
|
-
|
|
154
|
+
protocolInitialized: this.protocol !== undefined,
|
|
73
155
|
pendingClientId: this.pendingClientId,
|
|
74
|
-
|
|
156
|
+
clientJoined: this.hasMember(this.pendingClientId),
|
|
75
157
|
waitingForLeaveOp: this.waitingForLeaveOp,
|
|
76
158
|
};
|
|
77
159
|
this.handler.logConnectionIssue("NoJoinOp", details);
|
|
@@ -80,9 +162,6 @@ export class ConnectionStateHandler {
|
|
|
80
162
|
get connectionState() {
|
|
81
163
|
return this._connectionState;
|
|
82
164
|
}
|
|
83
|
-
get connected() {
|
|
84
|
-
return this.connectionState === ConnectionState.Connected;
|
|
85
|
-
}
|
|
86
165
|
get clientId() {
|
|
87
166
|
return this._clientId;
|
|
88
167
|
}
|
|
@@ -125,7 +204,7 @@ export class ConnectionStateHandler {
|
|
|
125
204
|
}
|
|
126
205
|
// Start the event in case we are waiting for leave or timeout.
|
|
127
206
|
if (this.waitingForLeaveOp) {
|
|
128
|
-
this.waitEvent = PerformanceEvent.start(this.logger, {
|
|
207
|
+
this.waitEvent = PerformanceEvent.start(this.handler.logger, {
|
|
129
208
|
eventName: "WaitBeforeClientLeave",
|
|
130
209
|
details: JSON.stringify({
|
|
131
210
|
waitOnClientId: this._clientId,
|
|
@@ -137,25 +216,21 @@ export class ConnectionStateHandler {
|
|
|
137
216
|
}
|
|
138
217
|
}
|
|
139
218
|
applyForConnectedState(source) {
|
|
140
|
-
var _a
|
|
141
|
-
|
|
142
|
-
assert(
|
|
143
|
-
assert(this.waitingForLeaveOp === false ||
|
|
144
|
-
(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" */);
|
|
145
222
|
// Move to connected state only if we are in Connecting state, we have seen our join op
|
|
146
223
|
// and there is no timer running which means we are not waiting for previous client to leave
|
|
147
224
|
// or timeout has occurred while doing so.
|
|
148
225
|
if (this.pendingClientId !== this.clientId
|
|
149
|
-
&& this.pendingClientId
|
|
150
|
-
&& quorumClients.getMember(this.pendingClientId) !== undefined
|
|
226
|
+
&& this.hasMember(this.pendingClientId)
|
|
151
227
|
&& !this.waitingForLeaveOp) {
|
|
152
228
|
(_a = this.waitEvent) === null || _a === void 0 ? void 0 : _a.end({ source });
|
|
153
|
-
|
|
154
|
-
this.catchUpMonitor.on("caughtUp", this.transitionToConnectedState);
|
|
229
|
+
this.setConnectionState(ConnectionState.Connected);
|
|
155
230
|
}
|
|
156
231
|
else {
|
|
157
232
|
// Adding this event temporarily so that we can get help debugging if something goes wrong.
|
|
158
|
-
this.logger.sendTelemetryEvent({
|
|
233
|
+
this.handler.logger.sendTelemetryEvent({
|
|
159
234
|
eventName: "connectedStateRejected",
|
|
160
235
|
category: source === "timeout" ? "error" : "generic",
|
|
161
236
|
details: JSON.stringify({
|
|
@@ -163,7 +238,7 @@ export class ConnectionStateHandler {
|
|
|
163
238
|
pendingClientId: this.pendingClientId,
|
|
164
239
|
clientId: this.clientId,
|
|
165
240
|
waitingForLeaveOp: this.waitingForLeaveOp,
|
|
166
|
-
|
|
241
|
+
clientJoined: this.hasMember(this.pendingClientId),
|
|
167
242
|
}),
|
|
168
243
|
});
|
|
169
244
|
}
|
|
@@ -187,18 +262,16 @@ export class ConnectionStateHandler {
|
|
|
187
262
|
* @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
|
|
188
263
|
* If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
|
|
189
264
|
*/
|
|
190
|
-
receivedConnectEvent(connectionMode, details
|
|
191
|
-
var _a;
|
|
265
|
+
receivedConnectEvent(connectionMode, details) {
|
|
192
266
|
const oldState = this._connectionState;
|
|
193
267
|
this._connectionState = ConnectionState.CatchingUp;
|
|
194
268
|
const writeConnection = connectionMode === "write";
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
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)" */);
|
|
202
275
|
// Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected
|
|
203
276
|
// (have received the join message for the client ID)
|
|
204
277
|
// This is especially important in the reconnect case. It's possible there could be outstanding
|
|
@@ -206,18 +279,14 @@ export class ConnectionStateHandler {
|
|
|
206
279
|
// join message. after we see the join message for our new connection with our new client id,
|
|
207
280
|
// we know there can no longer be outstanding ops that we sent with the previous client id.
|
|
208
281
|
this._pendingClientId = details.clientId;
|
|
209
|
-
// We may want to catch up to known ops as of now before transitioning to Connected state
|
|
210
|
-
this.catchUpMonitor = deltaManager !== undefined
|
|
211
|
-
? new CatchUpMonitor(deltaManager)
|
|
212
|
-
: new ImmediateCatchUpMonitor();
|
|
213
282
|
// IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
|
|
214
|
-
this.handler.
|
|
283
|
+
this.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState);
|
|
215
284
|
// For write connections, this pending clientId could be in the quorum already (i.e. join op already processed).
|
|
216
285
|
// We are fetching ops from storage in parallel to connecting to Relay Service,
|
|
217
286
|
// and given async processes, it's possible that we have already processed our own join message before
|
|
218
287
|
// connection was fully established.
|
|
219
|
-
// If
|
|
220
|
-
const waitingForJoinOp = writeConnection &&
|
|
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);
|
|
221
290
|
if (waitingForJoinOp) {
|
|
222
291
|
// Previous client left, and we are waiting for our own join op. When it is processed we'll join the quorum
|
|
223
292
|
// and attempt to transition to Connected state via receivedAddMemberEvent.
|
|
@@ -225,33 +294,24 @@ export class ConnectionStateHandler {
|
|
|
225
294
|
}
|
|
226
295
|
else if (!this.waitingForLeaveOp) {
|
|
227
296
|
// We're not waiting for Join or Leave op (if read-only connection those don't even apply),
|
|
228
|
-
//
|
|
229
|
-
|
|
297
|
+
// go ahead and declare the state to be Connected!
|
|
298
|
+
// If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.
|
|
299
|
+
this.setConnectionState(ConnectionState.Connected);
|
|
230
300
|
}
|
|
231
301
|
// else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later
|
|
232
302
|
}
|
|
233
|
-
/** Clear all the state used during the Connecting phase (set in receivedConnectEvent) */
|
|
234
|
-
clearPendingConnectionState() {
|
|
235
|
-
var _a;
|
|
236
|
-
this._pendingClientId = undefined;
|
|
237
|
-
(_a = this.catchUpMonitor) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
238
|
-
this.catchUpMonitor = undefined;
|
|
239
|
-
if (this.joinOpTimer.hasTimer) {
|
|
240
|
-
this.stopJoinOpTimer();
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
303
|
setConnectionState(value, reason) {
|
|
304
|
+
var _a, _b;
|
|
244
305
|
if (this.connectionState === value) {
|
|
245
306
|
// Already in the desired state - exit early
|
|
246
|
-
this.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
|
|
307
|
+
this.handler.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
|
|
247
308
|
return;
|
|
248
309
|
}
|
|
249
310
|
const oldState = this._connectionState;
|
|
250
311
|
this._connectionState = value;
|
|
251
|
-
const quorumClients = this.handler.quorumClients();
|
|
252
312
|
let client;
|
|
253
313
|
if (this._clientId !== undefined) {
|
|
254
|
-
client =
|
|
314
|
+
client = (_b = (_a = this.protocol) === null || _a === void 0 ? void 0 : _a.quorum) === null || _b === void 0 ? void 0 : _b.getMember(this._clientId);
|
|
255
315
|
}
|
|
256
316
|
if (value === ConnectionState.Connected) {
|
|
257
317
|
assert(oldState === ConnectionState.CatchingUp, 0x1d8 /* "Should only transition from Connecting state" */);
|
|
@@ -263,23 +323,26 @@ export class ConnectionStateHandler {
|
|
|
263
323
|
}
|
|
264
324
|
else if (value === ConnectionState.Disconnected) {
|
|
265
325
|
// Clear pending state immediately to prepare for reconnect
|
|
266
|
-
this.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
326
|
+
this._pendingClientId = undefined;
|
|
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
|
|
271
332
|
// we could receive "Disconnected" event multiple times without getting connected and in that case we
|
|
272
333
|
// don't want to reset the timer as we still want to wait on original client which started this timer.
|
|
273
334
|
if (client !== undefined
|
|
274
335
|
&& this.handler.shouldClientJoinWrite()
|
|
275
|
-
&& this.prevClientLeftTimer.hasTimer
|
|
336
|
+
&& !this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer
|
|
337
|
+
) {
|
|
276
338
|
this.prevClientLeftTimer.restart();
|
|
277
339
|
}
|
|
278
340
|
else {
|
|
279
341
|
// Adding this event temporarily so that we can get help debugging if something goes wrong.
|
|
280
|
-
this.logger.sendTelemetryEvent({
|
|
342
|
+
this.handler.logger.sendTelemetryEvent({
|
|
281
343
|
eventName: "noWaitOnDisconnected",
|
|
282
344
|
details: JSON.stringify({
|
|
345
|
+
clientId: this._clientId,
|
|
283
346
|
inQuorum: client !== undefined,
|
|
284
347
|
waitingForLeaveOp: this.waitingForLeaveOp,
|
|
285
348
|
hadOutstandingOps: this.handler.shouldClientJoinWrite(),
|
|
@@ -288,21 +351,38 @@ export class ConnectionStateHandler {
|
|
|
288
351
|
}
|
|
289
352
|
}
|
|
290
353
|
// Report transition before we propagate event across layers
|
|
291
|
-
this.handler.
|
|
292
|
-
|
|
293
|
-
|
|
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;
|
|
294
362
|
}
|
|
295
363
|
initProtocol(protocol) {
|
|
296
|
-
|
|
364
|
+
var _a, _b;
|
|
365
|
+
this.protocol = protocol;
|
|
366
|
+
(_a = this.membership) === null || _a === void 0 ? void 0 : _a.on("addMember", (clientId) => {
|
|
297
367
|
this.receivedAddMemberEvent(clientId);
|
|
298
368
|
});
|
|
299
|
-
|
|
369
|
+
(_b = this.membership) === null || _b === void 0 ? void 0 : _b.on("removeMember", (clientId) => {
|
|
300
370
|
this.receivedRemoveMemberEvent(clientId);
|
|
301
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
|
+
}
|
|
302
378
|
// if we have a clientId from a previous container we need to wait for its leave message
|
|
303
|
-
if (this.clientId !== undefined &&
|
|
379
|
+
if (this.clientId !== undefined && this.hasMember(this.clientId)) {
|
|
304
380
|
this.prevClientLeftTimer.restart();
|
|
305
381
|
}
|
|
306
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
|
+
}
|
|
307
387
|
}
|
|
308
388
|
//# sourceMappingURL=connectionStateHandler.js.map
|