17
17
* limitations under the License.
18
18
*/
19
19
20
- import { newError , SERVICE_UNAVAILABLE } from '../error' ;
20
+ import { newError , SERVICE_UNAVAILABLE , SESSION_EXPIRED } from '../error' ;
21
21
import { READ , WRITE } from '../driver' ;
22
22
import Session from '../session' ;
23
23
import RoundRobinArray from './round-robin-array' ;
@@ -70,16 +70,17 @@ export class LoadBalancer extends ConnectionProvider {
70
70
this . _connectionPool = connectionPool ;
71
71
this . _driverOnErrorCallback = driverOnErrorCallback ;
72
72
this . _hostNameResolver = LoadBalancer . _createHostNameResolver ( ) ;
73
+ this . _useSeedRouter = false ;
73
74
}
74
75
75
- acquireConnection ( mode ) {
76
- const connectionPromise = this . _freshRoutingTable ( ) . then ( routingTable => {
77
- if ( mode === READ ) {
76
+ acquireConnection ( accessMode ) {
77
+ const connectionPromise = this . _freshRoutingTable ( accessMode ) . then ( routingTable => {
78
+ if ( accessMode === READ ) {
78
79
return this . _acquireConnectionToServer ( routingTable . readers , 'read' ) ;
79
- } else if ( mode === WRITE ) {
80
+ } else if ( accessMode === WRITE ) {
80
81
return this . _acquireConnectionToServer ( routingTable . writers , 'write' ) ;
81
82
} else {
82
- throw newError ( 'Illegal mode ' + mode ) ;
83
+ throw newError ( 'Illegal mode ' + accessMode ) ;
83
84
}
84
85
} ) ;
85
86
return this . _withAdditionalOnErrorCallback ( connectionPromise , this . _driverOnErrorCallback ) ;
@@ -97,15 +98,17 @@ export class LoadBalancer extends ConnectionProvider {
97
98
_acquireConnectionToServer ( serversRoundRobinArray , serverName ) {
98
99
const address = serversRoundRobinArray . next ( ) ;
99
100
if ( ! address ) {
100
- return Promise . reject ( newError ( 'No ' + serverName + ' servers available' , SERVICE_UNAVAILABLE ) ) ;
101
+ return Promise . reject ( newError (
102
+ `Failed to obtain connection towards ${ serverName } server. Known routing table is: ${ this . _routingTable } ` ,
103
+ SESSION_EXPIRED ) ) ;
101
104
}
102
105
return this . _connectionPool . acquire ( address ) ;
103
106
}
104
107
105
- _freshRoutingTable ( ) {
108
+ _freshRoutingTable ( accessMode ) {
106
109
const currentRoutingTable = this . _routingTable ;
107
110
108
- if ( ! currentRoutingTable . isStale ( ) ) {
111
+ if ( ! currentRoutingTable . isStaleFor ( accessMode ) ) {
109
112
return Promise . resolve ( currentRoutingTable ) ;
110
113
}
111
114
return this . _refreshRoutingTable ( currentRoutingTable ) ;
@@ -114,48 +117,73 @@ export class LoadBalancer extends ConnectionProvider {
114
117
_refreshRoutingTable ( currentRoutingTable ) {
115
118
const knownRouters = currentRoutingTable . routers . toArray ( ) ;
116
119
117
- return this . _fetchNewRoutingTable ( knownRouters , currentRoutingTable ) . then ( newRoutingTable => {
118
- if ( LoadBalancer . _isValidRoutingTable ( newRoutingTable ) ) {
119
- // one of the known routers returned a valid routing table - use it
120
+ if ( this . _useSeedRouter ) {
121
+ return this . _fetchRoutingTableFromSeedRouterFallbackToKnownRouters ( knownRouters , currentRoutingTable ) ;
122
+ }
123
+ return this . _fetchRoutingTableFromKnownRoutersFallbackToSeedRouter ( knownRouters , currentRoutingTable ) ;
124
+ }
125
+
126
+ _fetchRoutingTableFromSeedRouterFallbackToKnownRouters ( knownRouters , currentRoutingTable ) {
127
+ // we start with seed router, no routers were probed before
128
+ const seenRouters = [ ] ;
129
+ return this . _fetchRoutingTableUsingSeedRouter ( seenRouters , this . _seedRouter ) . then ( newRoutingTable => {
130
+ if ( newRoutingTable ) {
131
+ this . _useSeedRouter = false ;
120
132
return newRoutingTable ;
121
133
}
122
134
123
- if ( ! newRoutingTable ) {
124
- // returned routing table was undefined, this means a connection error happened and the last known
125
- // router did not return a valid routing table, so we need to forget it
126
- const lastRouterIndex = knownRouters . length - 1 ;
127
- LoadBalancer . _forgetRouter ( currentRoutingTable , knownRouters , lastRouterIndex ) ;
135
+ // seed router did not return a valid routing table - try to use other known routers
136
+ return this . _fetchRoutingTableUsingKnownRouters ( knownRouters , currentRoutingTable ) ;
137
+ } ) . then ( newRoutingTable => {
138
+ this . _applyRoutingTableIfPossible ( newRoutingTable ) ;
139
+ return newRoutingTable ;
140
+ } ) ;
141
+ }
142
+
143
+ _fetchRoutingTableFromKnownRoutersFallbackToSeedRouter ( knownRouters , currentRoutingTable ) {
144
+ return this . _fetchRoutingTableUsingKnownRouters ( knownRouters , currentRoutingTable ) . then ( newRoutingTable => {
145
+ if ( newRoutingTable ) {
146
+ return newRoutingTable ;
128
147
}
129
148
130
149
// none of the known routers returned a valid routing table - try to use seed router address for rediscovery
131
- return this . _fetchNewRoutingTableUsingSeedRouterAddress ( knownRouters , this . _seedRouter ) ;
150
+ return this . _fetchRoutingTableUsingSeedRouter ( knownRouters , this . _seedRouter ) ;
132
151
} ) . then ( newRoutingTable => {
133
- if ( LoadBalancer . _isValidRoutingTable ( newRoutingTable ) ) {
134
- this . _updateRoutingTable ( newRoutingTable ) ;
152
+ this . _applyRoutingTableIfPossible ( newRoutingTable ) ;
153
+ return newRoutingTable ;
154
+ } ) ;
155
+ }
156
+
157
+ _fetchRoutingTableUsingKnownRouters ( knownRouters , currentRoutingTable ) {
158
+ return this . _fetchRoutingTable ( knownRouters , currentRoutingTable ) . then ( newRoutingTable => {
159
+ if ( newRoutingTable ) {
160
+ // one of the known routers returned a valid routing table - use it
135
161
return newRoutingTable ;
136
162
}
137
163
138
- // none of the existing routers returned valid routing table, throw exception
139
- throw newError ( 'Could not perform discovery. No routing servers available.' , SERVICE_UNAVAILABLE ) ;
164
+ // returned routing table was undefined, this means a connection error happened and the last known
165
+ // router did not return a valid routing table, so we need to forget it
166
+ const lastRouterIndex = knownRouters . length - 1 ;
167
+ LoadBalancer . _forgetRouter ( currentRoutingTable , knownRouters , lastRouterIndex ) ;
168
+
169
+ return null ;
140
170
} ) ;
141
171
}
142
172
143
- _fetchNewRoutingTableUsingSeedRouterAddress ( knownRouters , seedRouter ) {
173
+ _fetchRoutingTableUsingSeedRouter ( seenRouters , seedRouter ) {
144
174
return this . _hostNameResolver . resolve ( seedRouter ) . then ( resolvedRouterAddresses => {
145
175
// filter out all addresses that we've already tried
146
- const newAddresses = resolvedRouterAddresses . filter ( address => knownRouters . indexOf ( address ) < 0 ) ;
147
- return this . _fetchNewRoutingTable ( newAddresses , null ) ;
176
+ const newAddresses = resolvedRouterAddresses . filter ( address => seenRouters . indexOf ( address ) < 0 ) ;
177
+ return this . _fetchRoutingTable ( newAddresses , null ) ;
148
178
} ) ;
149
179
}
150
180
151
- _fetchNewRoutingTable ( routerAddresses , routingTable ) {
181
+ _fetchRoutingTable ( routerAddresses , routingTable ) {
152
182
return routerAddresses . reduce ( ( refreshedTablePromise , currentRouter , currentIndex ) => {
153
183
return refreshedTablePromise . then ( newRoutingTable => {
154
184
if ( newRoutingTable ) {
155
- if ( ! newRoutingTable . writers . isEmpty ( ) ) {
156
- // valid routing table was fetched - just return it, try next router otherwise
157
- return newRoutingTable ;
158
- }
185
+ // valid routing table was fetched - just return it, try next router otherwise
186
+ return newRoutingTable ;
159
187
} else {
160
188
// returned routing table was undefined, this means a connection error happened and we need to forget the
161
189
// previous router and try the next one
@@ -179,6 +207,23 @@ export class LoadBalancer extends ConnectionProvider {
179
207
return new Session ( READ , connectionProvider ) ;
180
208
}
181
209
210
+ _applyRoutingTableIfPossible ( newRoutingTable ) {
211
+ if ( ! newRoutingTable ) {
212
+ // none of routing servers returned valid routing table, throw exception
213
+ throw newError (
214
+ `Could not perform discovery. No routing servers available. Known routing table: ${ this . _routingTable } ` ,
215
+ SERVICE_UNAVAILABLE ) ;
216
+ }
217
+
218
+ if ( newRoutingTable . writers . isEmpty ( ) ) {
219
+ // use seed router next time. this is important when cluster is partitioned. it tries to make sure driver
220
+ // does not always get routing table without writers because it talks exclusively to a minority partition
221
+ this . _useSeedRouter = true ;
222
+ }
223
+
224
+ this . _updateRoutingTable ( newRoutingTable ) ;
225
+ }
226
+
182
227
_updateRoutingTable ( newRoutingTable ) {
183
228
const currentRoutingTable = this . _routingTable ;
184
229
@@ -190,10 +235,6 @@ export class LoadBalancer extends ConnectionProvider {
190
235
this . _routingTable = newRoutingTable ;
191
236
}
192
237
193
- static _isValidRoutingTable ( routingTable ) {
194
- return routingTable && ! routingTable . writers . isEmpty ( ) ;
195
- }
196
-
197
238
static _forgetRouter ( routingTable , routersArray , routerIndex ) {
198
239
const address = routersArray [ routerIndex ] ;
199
240
if ( routingTable && address ) {
0 commit comments