@@ -15,21 +15,38 @@ export const MIN_SECONDARY_WRITE_WIRE_VERSION = 13;
1515export type ServerSelector = (
1616 topologyDescription : TopologyDescription ,
1717 servers : ServerDescription [ ] ,
18- deprioritized ? : ServerDescription [ ]
18+ deprioritized : ServerDescription [ ]
1919) => ServerDescription [ ] ;
2020
21+ function sdEquals ( a : ServerDescription , b : ServerDescription ) {
22+ return a . address === b . address && a . equals ( b ) ;
23+ }
24+ function filterDeprioritized (
25+ candidates : ServerDescription [ ] ,
26+ deprioritized : ServerDescription [ ]
27+ ) : ServerDescription [ ] {
28+ const filtered = candidates . filter (
29+ candidate => ! deprioritized . some ( serverDescription => sdEquals ( serverDescription , candidate ) )
30+ ) ;
31+
32+ return filtered . length ? filtered : candidates ;
33+ }
34+
2135/**
2236 * Returns a server selector that selects for writable servers
2337 */
2438export function writableServerSelector ( ) : ServerSelector {
2539 return function writableServer (
2640 topologyDescription : TopologyDescription ,
27- servers : ServerDescription [ ]
41+ servers : ServerDescription [ ] ,
42+ deprioritized : ServerDescription [ ]
2843 ) : ServerDescription [ ] {
29- return latencyWindowReducer (
30- topologyDescription ,
31- servers . filter ( ( s : ServerDescription ) => s . isWritable )
44+ const eligibleServers = filterDeprioritized (
45+ servers . filter ( ( { isWritable } ) => isWritable ) ,
46+ deprioritized
3247 ) ;
48+
49+ return latencyWindowReducer ( topologyDescription , eligibleServers ) ;
3350 } ;
3451}
3552
@@ -39,8 +56,9 @@ export function writableServerSelector(): ServerSelector {
3956 */
4057export function sameServerSelector ( description ?: ServerDescription ) : ServerSelector {
4158 return function sameServerSelector (
42- topologyDescription : TopologyDescription ,
43- servers : ServerDescription [ ]
59+ _topologyDescription : TopologyDescription ,
60+ servers : ServerDescription [ ] ,
61+ _deprioritized : ServerDescription [ ]
4462 ) : ServerDescription [ ] {
4563 if ( ! description ) return [ ] ;
4664 // Filter the servers to match the provided description only if
@@ -218,10 +236,7 @@ function latencyWindowReducer(
218236 ) ;
219237
220238 const high = low + topologyDescription . localThresholdMS ;
221- return servers . reduce ( ( result : ServerDescription [ ] , server : ServerDescription ) => {
222- if ( server . roundTripTime <= high && server . roundTripTime >= low ) result . push ( server ) ;
223- return result ;
224- } , [ ] ) ;
239+ return servers . filter ( server => server . roundTripTime <= high && server . roundTripTime >= low ) ;
225240}
226241
227242// filters
@@ -245,6 +260,18 @@ function loadBalancerFilter(server: ServerDescription): boolean {
245260 return server . type === ServerType . LoadBalancer ;
246261}
247262
263+ function isDeprioritizedFactory (
264+ deprioritized : ServerDescription [ ]
265+ ) : ( server : ServerDescription ) => boolean {
266+ return server =>
267+ // if any deprioritized servers equal the server, here we are.
268+ ! deprioritized . some ( deprioritizedServer => {
269+ const result = sdEquals ( deprioritizedServer , server ) ;
270+ // console.error(result);
271+ return result ;
272+ } ) ;
273+ }
274+
248275/**
249276 * Returns a function which selects servers based on a provided read preference
250277 *
@@ -258,7 +285,7 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se
258285 return function readPreferenceServers (
259286 topologyDescription : TopologyDescription ,
260287 servers : ServerDescription [ ] ,
261- deprioritized : ServerDescription [ ] = [ ]
288+ deprioritized : ServerDescription [ ]
262289 ) : ServerDescription [ ] {
263290 if ( topologyDescription . type === TopologyType . LoadBalanced ) {
264291 return servers . filter ( loadBalancerFilter ) ;
@@ -273,38 +300,79 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se
273300 }
274301
275302 if ( topologyDescription . type === TopologyType . Sharded ) {
276- const filtered = servers . filter ( server => {
277- return ! deprioritized . includes ( server ) ;
278- } ) ;
279- const selectable = filtered . length > 0 ? filtered : deprioritized ;
303+ const selectable = filterDeprioritized ( servers , deprioritized ) ;
280304 return latencyWindowReducer ( topologyDescription , selectable . filter ( knownFilter ) ) ;
281305 }
282306
283307 const mode = readPreference . mode ;
284308 if ( mode === ReadPreference . PRIMARY ) {
285- return servers . filter ( primaryFilter ) ;
309+ return filterDeprioritized ( servers . filter ( primaryFilter ) , deprioritized ) ;
286310 }
287311
288312 if ( mode === ReadPreference . PRIMARY_PREFERRED ) {
289- const result = servers . filter ( primaryFilter ) ;
290- if ( result . length ) {
291- return result ;
313+ const primary = servers . filter ( primaryFilter ) ;
314+
315+ // If there is a primary and it is not deprioritized, use the primary. Otherwise,
316+ // check for secondaries.
317+ const eligiblePrimary = primary . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
318+ if ( eligiblePrimary . length ) {
319+ return eligiblePrimary ;
292320 }
293- }
294321
295- const filter = mode === ReadPreference . NEAREST ? nearestFilter : secondaryFilter ;
296- const selectedServers = latencyWindowReducer (
297- topologyDescription ,
298- tagSetReducer (
322+ const secondaries = tagSetReducer (
299323 readPreference ,
300- maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( filter ) )
301- )
324+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( secondaryFilter ) )
325+ ) ;
326+ const deprioritizedSecondaries = secondaries . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
327+
328+ // console.error({ deprioritizedSecondaries, secondaries, deprioritized });
329+ if ( deprioritizedSecondaries . length )
330+ return latencyWindowReducer ( topologyDescription , deprioritizedSecondaries ) ;
331+
332+ // if we make it here, we have no primaries or secondaries that not deprioritized.
333+ // prefer the primary (which may not exist, if the topology has no primary).
334+ // otherwise, return the secondaries (which also may not exist, but there is nothing else to check here).
335+ return primary . length ? primary : latencyWindowReducer ( topologyDescription , secondaries ) ;
336+ }
337+
338+ // TODO: should we be applying the latency window to nearest servers?
339+ if ( mode === 'nearest' ) {
340+ // if read preference is nearest
341+ return latencyWindowReducer (
342+ topologyDescription ,
343+ filterDeprioritized (
344+ tagSetReducer (
345+ readPreference ,
346+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( nearestFilter ) )
347+ ) ,
348+ deprioritized
349+ )
350+ ) ;
351+ }
352+
353+ const filter = secondaryFilter ;
354+
355+ const secondaries = tagSetReducer (
356+ readPreference ,
357+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( filter ) )
302358 ) ;
359+ const eligibleSecondaries = secondaries . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
360+
361+ if ( eligibleSecondaries . length )
362+ return latencyWindowReducer ( topologyDescription , eligibleSecondaries ) ;
363+
364+ // we have no eligible secondaries, try for a primary.
365+ if ( mode === ReadPreference . SECONDARY_PREFERRED ) {
366+ const primary = servers . filter ( primaryFilter ) ;
367+ const eligiblePrimary = primary . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
368+
369+ if ( eligiblePrimary . length ) return eligiblePrimary ;
303370
304- if ( mode === ReadPreference . SECONDARY_PREFERRED && selectedServers . length === 0 ) {
305- return servers . filter ( primaryFilter ) ;
371+ // we have no eligible primary nor secondaries that have not been deprioritized
372+ return secondaries . length ? latencyWindowReducer ( topologyDescription , secondaries ) : primary ;
306373 }
307374
308- return selectedServers ;
375+ // return all secondaries in the latency window.
376+ return latencyWindowReducer ( topologyDescription , secondaries ) ;
309377 } ;
310378}
0 commit comments