Skip to content

Commit 00dcf2d

Browse files
authored
fix(NODE-4159,NODE-4512): remove servers with incorrect setName from topology and fix unix socket parsing (#3348)
1 parent 38403d0 commit 00dcf2d

11 files changed

+199
-179
lines changed

src/sdam/server.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
MongoNetworkError,
3030
MongoNetworkTimeoutError,
3131
MongoServerClosedError,
32+
MongoServerError,
3233
MongoUnexpectedServerResponseError,
3334
needsRetryableWriteLabel
3435
} from '../error';
@@ -385,7 +386,7 @@ function calculateRoundTripTime(oldRtt: number, duration: number): number {
385386
return alpha * duration + (1 - alpha) * oldRtt;
386387
}
387388

388-
function markServerUnknown(server: Server, error?: MongoError) {
389+
function markServerUnknown(server: Server, error?: MongoServerError) {
389390
// Load balancer servers can never be marked unknown.
390391
if (server.loadBalanced) {
391392
return;
@@ -397,11 +398,7 @@ function markServerUnknown(server: Server, error?: MongoError) {
397398

398399
server.emit(
399400
Server.DESCRIPTION_RECEIVED,
400-
new ServerDescription(server.description.hostAddress, undefined, {
401-
error,
402-
topologyVersion:
403-
error && error.topologyVersion ? error.topologyVersion : server.description.topologyVersion
404-
})
401+
new ServerDescription(server.description.hostAddress, undefined, { error })
405402
);
406403
}
407404

src/sdam/server_description.ts

Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Document, Long, ObjectId } from '../bson';
2-
import type { MongoError } from '../error';
2+
import { MongoRuntimeError, MongoServerError } from '../error';
33
import { arrayStrictEqual, compareObjectId, errorStrictEqual, HostAddress, now } from '../utils';
44
import type { ClusterTime } from './common';
55
import { ServerType } from './common';
@@ -31,14 +31,11 @@ export type TagSet = { [key: string]: string };
3131
/** @internal */
3232
export interface ServerDescriptionOptions {
3333
/** An Error used for better reporting debugging */
34-
error?: MongoError;
34+
error?: MongoServerError;
3535

3636
/** The round trip time to ping this server (in ms) */
3737
roundTripTime?: number;
3838

39-
/** The topologyVersion */
40-
topologyVersion?: TopologyVersion;
41-
4239
/** If the client is in load balancing mode. */
4340
loadBalanced?: boolean;
4441
}
@@ -50,28 +47,25 @@ export interface ServerDescriptionOptions {
5047
* @public
5148
*/
5249
export class ServerDescription {
53-
private _hostAddress: HostAddress;
5450
address: string;
5551
type: ServerType;
5652
hosts: string[];
5753
passives: string[];
5854
arbiters: string[];
5955
tags: TagSet;
60-
61-
error?: MongoError;
62-
topologyVersion?: TopologyVersion;
56+
error: MongoServerError | null;
57+
topologyVersion: TopologyVersion | null;
6358
minWireVersion: number;
6459
maxWireVersion: number;
6560
roundTripTime: number;
6661
lastUpdateTime: number;
6762
lastWriteDate: number;
68-
69-
me?: string;
70-
primary?: string;
71-
setName?: string;
72-
setVersion?: number;
73-
electionId?: ObjectId;
74-
logicalSessionTimeoutMinutes?: number;
63+
me: string | null;
64+
primary: string | null;
65+
setName: string | null;
66+
setVersion: number | null;
67+
electionId: ObjectId | null;
68+
logicalSessionTimeoutMinutes: number | null;
7569

7670
// NOTE: does this belong here? It seems we should gossip the cluster time at the CMAP level
7771
$clusterTime?: ClusterTime;
@@ -83,14 +77,19 @@ export class ServerDescription {
8377
* @param address - The address of the server
8478
* @param hello - An optional hello response for this server
8579
*/
86-
constructor(address: HostAddress | string, hello?: Document, options?: ServerDescriptionOptions) {
87-
if (typeof address === 'string') {
88-
this._hostAddress = new HostAddress(address);
89-
this.address = this._hostAddress.toString();
90-
} else {
91-
this._hostAddress = address;
92-
this.address = this._hostAddress.toString();
80+
constructor(
81+
address: HostAddress | string,
82+
hello?: Document,
83+
options: ServerDescriptionOptions = {}
84+
) {
85+
if (address == null || address === '') {
86+
throw new MongoRuntimeError('ServerDescription must be provided with a non-empty address');
9387
}
88+
89+
this.address =
90+
typeof address === 'string'
91+
? HostAddress.fromString(address).toString(false) // Use HostAddress to normalize
92+
: address.toString(false);
9493
this.type = parseServerType(hello, options);
9594
this.hosts = hello?.hosts?.map((host: string) => host.toLowerCase()) ?? [];
9695
this.passives = hello?.passives?.map((host: string) => host.toLowerCase()) ?? [];
@@ -101,50 +100,20 @@ export class ServerDescription {
101100
this.roundTripTime = options?.roundTripTime ?? -1;
102101
this.lastUpdateTime = now();
103102
this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
104-
105-
if (options?.topologyVersion) {
106-
this.topologyVersion = options.topologyVersion;
107-
} else if (hello?.topologyVersion) {
108-
// TODO(NODE-2674): Preserve int64 sent from MongoDB
109-
this.topologyVersion = hello.topologyVersion;
110-
}
111-
112-
if (options?.error) {
113-
this.error = options.error;
114-
}
115-
116-
if (hello?.primary) {
117-
this.primary = hello.primary;
118-
}
119-
120-
if (hello?.me) {
121-
this.me = hello.me.toLowerCase();
122-
}
123-
124-
if (hello?.setName) {
125-
this.setName = hello.setName;
126-
}
127-
128-
if (hello?.setVersion) {
129-
this.setVersion = hello.setVersion;
130-
}
131-
132-
if (hello?.electionId) {
133-
this.electionId = hello.electionId;
134-
}
135-
136-
if (hello?.logicalSessionTimeoutMinutes) {
137-
this.logicalSessionTimeoutMinutes = hello.logicalSessionTimeoutMinutes;
138-
}
139-
140-
if (hello?.$clusterTime) {
141-
this.$clusterTime = hello.$clusterTime;
142-
}
103+
this.error = options.error ?? null;
104+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
105+
this.topologyVersion = this.error?.topologyVersion ?? hello?.topologyVersion ?? null;
106+
this.setName = hello?.setName ?? null;
107+
this.setVersion = hello?.setVersion ?? null;
108+
this.electionId = hello?.electionId ?? null;
109+
this.logicalSessionTimeoutMinutes = hello?.logicalSessionTimeoutMinutes ?? null;
110+
this.primary = hello?.primary ?? null;
111+
this.me = hello?.me?.toLowerCase() ?? null;
112+
this.$clusterTime = hello?.$clusterTime ?? null;
143113
}
144114

145115
get hostAddress(): HostAddress {
146-
if (this._hostAddress) return this._hostAddress;
147-
else return new HostAddress(this.address);
116+
return HostAddress.fromString(this.address);
148117
}
149118

150119
get allHosts(): string[] {
@@ -181,7 +150,8 @@ export class ServerDescription {
181150
* in the {@link https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription|SDAM spec}
182151
*/
183152
equals(other?: ServerDescription | null): boolean {
184-
// TODO(NODE-4510): Check ServerDescription equality logic for nullish topologyVersion meaning "greater than"
153+
// Despite using the comparator that would determine a nullish topologyVersion as greater than
154+
// for equality we should only always perform direct equality comparison
185155
const topologyVersionsEqual =
186156
this.topologyVersion === other?.topologyVersion ||
187157
compareTopologyVersion(this.topologyVersion, other?.topologyVersion) === 0;
@@ -271,8 +241,8 @@ function tagsStrictEqual(tags: TagSet, tags2: TagSet): boolean {
271241
* ```
272242
*/
273243
export function compareTopologyVersion(
274-
currentTv?: TopologyVersion,
275-
newTv?: TopologyVersion
244+
currentTv?: TopologyVersion | null,
245+
newTv?: TopologyVersion | null
276246
): 0 | -1 | 1 {
277247
if (currentTv == null || newTv == null) {
278248
return -1;

src/sdam/topology.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
724724
return this.description.commonWireVersion;
725725
}
726726

727-
get logicalSessionTimeoutMinutes(): number | undefined {
727+
get logicalSessionTimeoutMinutes(): number | null {
728728
return this.description.logicalSessionTimeoutMinutes;
729729
}
730730

src/sdam/topology_description.ts

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ObjectId } from '../bson';
22
import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants';
3-
import { MongoError, MongoRuntimeError } from '../error';
3+
import { MongoRuntimeError, MongoServerError } from '../error';
44
import { compareObjectId, shuffle } from '../utils';
55
import { ServerType, TopologyType } from './common';
66
import { ServerDescription } from './server_description';
@@ -32,29 +32,29 @@ export interface TopologyDescriptionOptions {
3232
*/
3333
export class TopologyDescription {
3434
type: TopologyType;
35-
setName?: string;
36-
maxSetVersion?: number;
37-
maxElectionId?: ObjectId;
35+
setName: string | null;
36+
maxSetVersion: number | null;
37+
maxElectionId: ObjectId | null;
3838
servers: Map<string, ServerDescription>;
3939
stale: boolean;
4040
compatible: boolean;
4141
compatibilityError?: string;
42-
logicalSessionTimeoutMinutes?: number;
42+
logicalSessionTimeoutMinutes: number | null;
4343
heartbeatFrequencyMS: number;
4444
localThresholdMS: number;
45-
commonWireVersion?: number;
45+
commonWireVersion: number;
4646

4747
/**
4848
* Create a TopologyDescription
4949
*/
5050
constructor(
5151
topologyType: TopologyType,
52-
serverDescriptions?: Map<string, ServerDescription>,
53-
setName?: string,
54-
maxSetVersion?: number,
55-
maxElectionId?: ObjectId,
56-
commonWireVersion?: number,
57-
options?: TopologyDescriptionOptions
52+
serverDescriptions: Map<string, ServerDescription> | null = null,
53+
setName: string | null = null,
54+
maxSetVersion: number | null = null,
55+
maxElectionId: ObjectId | null = null,
56+
commonWireVersion: number | null = null,
57+
options: TopologyDescriptionOptions | null = null
5858
) {
5959
options = options ?? {};
6060

@@ -64,22 +64,10 @@ export class TopologyDescription {
6464
this.compatible = true;
6565
this.heartbeatFrequencyMS = options.heartbeatFrequencyMS ?? 0;
6666
this.localThresholdMS = options.localThresholdMS ?? 15;
67-
68-
if (setName) {
69-
this.setName = setName;
70-
}
71-
72-
if (maxSetVersion) {
73-
this.maxSetVersion = maxSetVersion;
74-
}
75-
76-
if (maxElectionId) {
77-
this.maxElectionId = maxElectionId;
78-
}
79-
80-
if (commonWireVersion) {
81-
this.commonWireVersion = commonWireVersion;
82-
}
67+
this.setName = setName ?? null;
68+
this.maxElectionId = maxElectionId ?? null;
69+
this.maxSetVersion = maxSetVersion ?? null;
70+
this.commonWireVersion = commonWireVersion ?? 0;
8371

8472
// determine server compatibility
8573
for (const serverDescription of this.servers.values()) {
@@ -108,12 +96,12 @@ export class TopologyDescription {
10896
// value among ServerDescriptions of all data-bearing server types. If any have a null
10997
// logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
11098
// set to null.
111-
this.logicalSessionTimeoutMinutes = undefined;
99+
this.logicalSessionTimeoutMinutes = null;
112100
for (const [, server] of this.servers) {
113101
if (server.isReadable) {
114102
if (server.logicalSessionTimeoutMinutes == null) {
115103
// If any of the servers have a null logicalSessionsTimeout, then the whole topology does
116-
this.logicalSessionTimeoutMinutes = undefined;
104+
this.logicalSessionTimeoutMinutes = null;
117105
break;
118106
}
119107

@@ -200,11 +188,6 @@ export class TopologyDescription {
200188
// potentially mutated values
201189
let { type: topologyType, setName, maxSetVersion, maxElectionId, commonWireVersion } = this;
202190

203-
if (serverDescription.setName && setName && serverDescription.setName !== setName) {
204-
// TODO(NODE-4159): servers with an incorrect setName should be removed not marked Unknown
205-
serverDescription = new ServerDescription(address, undefined);
206-
}
207-
208191
const serverType = serverDescription.type;
209192
const serverDescriptions = new Map(this.servers);
210193

@@ -217,6 +200,19 @@ export class TopologyDescription {
217200
}
218201
}
219202

203+
if (
204+
typeof serverDescription.setName === 'string' &&
205+
typeof setName === 'string' &&
206+
serverDescription.setName !== setName
207+
) {
208+
if (topologyType === TopologyType.Single) {
209+
// "Single" Topology with setName mismatch is direct connection usage, mark unknown do not remove
210+
serverDescription = new ServerDescription(address);
211+
} else {
212+
serverDescriptions.delete(address);
213+
}
214+
}
215+
220216
// update the actual server description
221217
serverDescriptions.set(address, serverDescription);
222218

@@ -311,15 +307,16 @@ export class TopologyDescription {
311307
);
312308
}
313309

314-
get error(): MongoError | undefined {
310+
get error(): MongoServerError | null {
315311
const descriptionsWithError = Array.from(this.servers.values()).filter(
316312
(sd: ServerDescription) => sd.error
317313
);
318314

319315
if (descriptionsWithError.length > 0) {
320316
return descriptionsWithError[0].error;
321317
}
322-
return;
318+
319+
return null;
323320
}
324321

325322
/**
@@ -366,10 +363,10 @@ function topologyTypeForServerType(serverType: ServerType): TopologyType {
366363
function updateRsFromPrimary(
367364
serverDescriptions: Map<string, ServerDescription>,
368365
serverDescription: ServerDescription,
369-
setName?: string,
370-
maxSetVersion?: number,
371-
maxElectionId?: ObjectId
372-
): [TopologyType, string?, number?, ObjectId?] {
366+
setName: string | null = null,
367+
maxSetVersion: number | null = null,
368+
maxElectionId: ObjectId | null = null
369+
): [TopologyType, string | null, number | null, ObjectId | null] {
373370
setName = setName || serverDescription.setName;
374371
if (setName !== serverDescription.setName) {
375372
serverDescriptions.delete(serverDescription.address);
@@ -436,7 +433,7 @@ function updateRsFromPrimary(
436433
function updateRsWithPrimaryFromMember(
437434
serverDescriptions: Map<string, ServerDescription>,
438435
serverDescription: ServerDescription,
439-
setName?: string
436+
setName: string | null = null
440437
): TopologyType {
441438
if (setName == null) {
442439
// TODO(NODE-3483): should be an appropriate runtime error
@@ -456,10 +453,10 @@ function updateRsWithPrimaryFromMember(
456453
function updateRsNoPrimaryFromMember(
457454
serverDescriptions: Map<string, ServerDescription>,
458455
serverDescription: ServerDescription,
459-
setName?: string
460-
): [TopologyType, string?] {
456+
setName: string | null = null
457+
): [TopologyType, string | null] {
461458
const topologyType = TopologyType.ReplicaSetNoPrimary;
462-
setName = setName || serverDescription.setName;
459+
setName = setName ?? serverDescription.setName;
463460
if (setName !== serverDescription.setName) {
464461
serverDescriptions.delete(serverDescription.address);
465462
return [topologyType, setName];

0 commit comments

Comments
 (0)