@@ -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
@@ -231,10 +249,7 @@ function latencyWindowReducer(
231249 ) ;
232250
233251 const high = low + topologyDescription . localThresholdMS ;
234- return servers . reduce ( ( result : ServerDescription [ ] , server : ServerDescription ) => {
235- if ( server . roundTripTime <= high && server . roundTripTime >= low ) result . push ( server ) ;
236- return result ;
237- } , [ ] ) ;
252+ return servers . filter ( server => server . roundTripTime <= high && server . roundTripTime >= low ) ;
238253}
239254
240255// filters
@@ -258,6 +273,18 @@ function loadBalancerFilter(server: ServerDescription): boolean {
258273 return server . type === ServerType . LoadBalancer ;
259274}
260275
276+ function isDeprioritizedFactory (
277+ deprioritized : ServerDescription [ ]
278+ ) : ( server : ServerDescription ) => boolean {
279+ return server =>
280+ // if any deprioritized servers equal the server, here we are.
281+ ! deprioritized . some ( deprioritizedServer => {
282+ const result = sdEquals ( deprioritizedServer , server ) ;
283+ // console.error(result);
284+ return result ;
285+ } ) ;
286+ }
287+
261288/**
262289 * Returns a function which selects servers based on a provided read preference
263290 *
@@ -271,7 +298,7 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se
271298 return function readPreferenceServers (
272299 topologyDescription : TopologyDescription ,
273300 servers : ServerDescription [ ] ,
274- deprioritized : ServerDescription [ ] = [ ]
301+ deprioritized : ServerDescription [ ]
275302 ) : ServerDescription [ ] {
276303 if ( topologyDescription . type === TopologyType . LoadBalanced ) {
277304 return servers . filter ( loadBalancerFilter ) ;
@@ -286,38 +313,79 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se
286313 }
287314
288315 if ( topologyDescription . type === TopologyType . Sharded ) {
289- const filtered = servers . filter ( server => {
290- return ! deprioritized . includes ( server ) ;
291- } ) ;
292- const selectable = filtered . length > 0 ? filtered : deprioritized ;
316+ const selectable = filterDeprioritized ( servers , deprioritized ) ;
293317 return latencyWindowReducer ( topologyDescription , selectable . filter ( knownFilter ) ) ;
294318 }
295319
296320 const mode = readPreference . mode ;
297321 if ( mode === ReadPreference . PRIMARY ) {
298- return servers . filter ( primaryFilter ) ;
322+ return filterDeprioritized ( servers . filter ( primaryFilter ) , deprioritized ) ;
299323 }
300324
301325 if ( mode === ReadPreference . PRIMARY_PREFERRED ) {
302- const result = servers . filter ( primaryFilter ) ;
303- if ( result . length ) {
304- return result ;
326+ const primary = servers . filter ( primaryFilter ) ;
327+
328+ // If there is a primary and it is not deprioritized, use the primary. Otherwise,
329+ // check for secondaries.
330+ const eligiblePrimary = primary . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
331+ if ( eligiblePrimary . length ) {
332+ return eligiblePrimary ;
305333 }
306- }
307334
308- const filter = mode === ReadPreference . NEAREST ? nearestFilter : secondaryFilter ;
309- const selectedServers = latencyWindowReducer (
310- topologyDescription ,
311- tagSetReducer (
335+ const secondaries = tagSetReducer (
312336 readPreference ,
313- maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( filter ) )
314- )
337+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( secondaryFilter ) )
338+ ) ;
339+ const deprioritizedSecondaries = secondaries . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
340+
341+ // console.error({ deprioritizedSecondaries, secondaries, deprioritized });
342+ if ( deprioritizedSecondaries . length )
343+ return latencyWindowReducer ( topologyDescription , deprioritizedSecondaries ) ;
344+
345+ // if we make it here, we have no primaries or secondaries that not deprioritized.
346+ // prefer the primary (which may not exist, if the topology has no primary).
347+ // otherwise, return the secondaries (which also may not exist, but there is nothing else to check here).
348+ return primary . length ? primary : latencyWindowReducer ( topologyDescription , secondaries ) ;
349+ }
350+
351+ // TODO: should we be applying the latency window to nearest servers?
352+ if ( mode === 'nearest' ) {
353+ // if read preference is nearest
354+ return latencyWindowReducer (
355+ topologyDescription ,
356+ filterDeprioritized (
357+ tagSetReducer (
358+ readPreference ,
359+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( nearestFilter ) )
360+ ) ,
361+ deprioritized
362+ )
363+ ) ;
364+ }
365+
366+ const filter = secondaryFilter ;
367+
368+ const secondaries = tagSetReducer (
369+ readPreference ,
370+ maxStalenessReducer ( readPreference , topologyDescription , servers . filter ( filter ) )
315371 ) ;
372+ const eligibleSecondaries = secondaries . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
373+
374+ if ( eligibleSecondaries . length )
375+ return latencyWindowReducer ( topologyDescription , eligibleSecondaries ) ;
376+
377+ // we have no eligible secondaries, try for a primary.
378+ if ( mode === ReadPreference . SECONDARY_PREFERRED ) {
379+ const primary = servers . filter ( primaryFilter ) ;
380+ const eligiblePrimary = primary . filter ( isDeprioritizedFactory ( deprioritized ) ) ;
381+
382+ if ( eligiblePrimary . length ) return eligiblePrimary ;
316383
317- if ( mode === ReadPreference . SECONDARY_PREFERRED && selectedServers . length === 0 ) {
318- return servers . filter ( primaryFilter ) ;
384+ // we have no eligible primary nor secondaries that have not been deprioritized
385+ return secondaries . length ? latencyWindowReducer ( topologyDescription , secondaries ) : primary ;
319386 }
320387
321- return selectedServers ;
388+ // return all secondaries in the latency window.
389+ return latencyWindowReducer ( topologyDescription , secondaries ) ;
322390 } ;
323391}
0 commit comments